]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/static/js/main.js
Rollup merge of #107476 - notriddle:notriddle/item-decl-3, r=GuillaumeGomez
[rust.git] / src / librustdoc / html / static / js / main.js
1 // Local js definitions:
2 /* global addClass, getSettingValue, hasClass, searchState */
3 /* global onEach, onEachLazy, removeClass */
4
5 "use strict";
6
7 // Get a value from the rustdoc-vars div, which is used to convey data from
8 // Rust to the JS. If there is no such element, return null.
9 function getVar(name) {
10     const el = document.getElementById("rustdoc-vars");
11     if (el) {
12         return el.attributes["data-" + name].value;
13     } else {
14         return null;
15     }
16 }
17
18 // Given a basename (e.g. "storage") and an extension (e.g. ".js"), return a URL
19 // for a resource under the root-path, with the resource-suffix.
20 function resourcePath(basename, extension) {
21     return getVar("root-path") + basename + getVar("resource-suffix") + extension;
22 }
23
24 function hideMain() {
25     addClass(document.getElementById(MAIN_ID), "hidden");
26 }
27
28 function showMain() {
29     removeClass(document.getElementById(MAIN_ID), "hidden");
30 }
31
32 function elemIsInParent(elem, parent) {
33     while (elem && elem !== document.body) {
34         if (elem === parent) {
35             return true;
36         }
37         elem = elem.parentElement;
38     }
39     return false;
40 }
41
42 function blurHandler(event, parentElem, hideCallback) {
43     if (!elemIsInParent(document.activeElement, parentElem) &&
44         !elemIsInParent(event.relatedTarget, parentElem)
45     ) {
46         hideCallback();
47     }
48 }
49
50 window.rootPath = getVar("root-path");
51 window.currentCrate = getVar("current-crate");
52
53 function setMobileTopbar() {
54     // FIXME: It would be nicer to generate this text content directly in HTML,
55     // but with the current code it's hard to get the right information in the right place.
56     const mobileLocationTitle = document.querySelector(".mobile-topbar h2");
57     const locationTitle = document.querySelector(".sidebar h2.location");
58     if (mobileLocationTitle && locationTitle) {
59         mobileLocationTitle.innerHTML = locationTitle.innerHTML;
60     }
61 }
62
63 // Gets the human-readable string for the virtual-key code of the
64 // given KeyboardEvent, ev.
65 //
66 // This function is meant as a polyfill for KeyboardEvent#key,
67 // since it is not supported in IE 11 or Chrome for Android. We also test for
68 // KeyboardEvent#keyCode because the handleShortcut handler is
69 // also registered for the keydown event, because Blink doesn't fire
70 // keypress on hitting the Escape key.
71 //
72 // So I guess you could say things are getting pretty interoperable.
73 function getVirtualKey(ev) {
74     if ("key" in ev && typeof ev.key !== "undefined") {
75         return ev.key;
76     }
77
78     const c = ev.charCode || ev.keyCode;
79     if (c === 27) {
80         return "Escape";
81     }
82     return String.fromCharCode(c);
83 }
84
85 const MAIN_ID = "main-content";
86 const SETTINGS_BUTTON_ID = "settings-menu";
87 const ALTERNATIVE_DISPLAY_ID = "alternative-display";
88 const NOT_DISPLAYED_ID = "not-displayed";
89 const HELP_BUTTON_ID = "help-button";
90
91 function getSettingsButton() {
92     return document.getElementById(SETTINGS_BUTTON_ID);
93 }
94
95 function getHelpButton() {
96     return document.getElementById(HELP_BUTTON_ID);
97 }
98
99 // Returns the current URL without any query parameter or hash.
100 function getNakedUrl() {
101     return window.location.href.split("?")[0].split("#")[0];
102 }
103
104 /**
105  * This function inserts `newNode` after `referenceNode`. It doesn't work if `referenceNode`
106  * doesn't have a parent node.
107  *
108  * @param {HTMLElement} newNode
109  * @param {HTMLElement} referenceNode
110  */
111 function insertAfter(newNode, referenceNode) {
112     referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
113 }
114
115 /**
116  * This function creates a new `<section>` with the given `id` and `classes` if it doesn't already
117  * exist.
118  *
119  * More information about this in `switchDisplayedElement` documentation.
120  *
121  * @param {string} id
122  * @param {string} classes
123  */
124 function getOrCreateSection(id, classes) {
125     let el = document.getElementById(id);
126
127     if (!el) {
128         el = document.createElement("section");
129         el.id = id;
130         el.className = classes;
131         insertAfter(el, document.getElementById(MAIN_ID));
132     }
133     return el;
134 }
135
136 /**
137  * Returns the `<section>` element which contains the displayed element.
138  *
139  * @return {HTMLElement}
140  */
141 function getAlternativeDisplayElem() {
142     return getOrCreateSection(ALTERNATIVE_DISPLAY_ID, "content hidden");
143 }
144
145 /**
146  * Returns the `<section>` element which contains the not-displayed elements.
147  *
148  * @return {HTMLElement}
149  */
150 function getNotDisplayedElem() {
151     return getOrCreateSection(NOT_DISPLAYED_ID, "hidden");
152 }
153
154 /**
155  * To nicely switch between displayed "extra" elements (such as search results or settings menu)
156  * and to alternate between the displayed and not displayed elements, we hold them in two different
157  * `<section>` elements. They work in pair: one holds the hidden elements while the other
158  * contains the displayed element (there can be only one at the same time!). So basically, we switch
159  * elements between the two `<section>` elements.
160  *
161  * @param {HTMLElement} elemToDisplay
162  */
163 function switchDisplayedElement(elemToDisplay) {
164     const el = getAlternativeDisplayElem();
165
166     if (el.children.length > 0) {
167         getNotDisplayedElem().appendChild(el.firstElementChild);
168     }
169     if (elemToDisplay === null) {
170         addClass(el, "hidden");
171         showMain();
172         return;
173     }
174     el.appendChild(elemToDisplay);
175     hideMain();
176     removeClass(el, "hidden");
177 }
178
179 function browserSupportsHistoryApi() {
180     return window.history && typeof window.history.pushState === "function";
181 }
182
183 // eslint-disable-next-line no-unused-vars
184 function loadCss(cssUrl) {
185     const link = document.createElement("link");
186     link.href = cssUrl;
187     link.rel = "stylesheet";
188     document.getElementsByTagName("head")[0].appendChild(link);
189 }
190
191 (function() {
192     const isHelpPage = window.location.pathname.endsWith("/help.html");
193
194     function loadScript(url) {
195         const script = document.createElement("script");
196         script.src = url;
197         document.head.append(script);
198     }
199
200     getSettingsButton().onclick = event => {
201         if (event.ctrlKey || event.altKey || event.metaKey) {
202             return;
203         }
204         window.hideAllModals(false);
205         addClass(getSettingsButton(), "rotate");
206         event.preventDefault();
207         // Sending request for the CSS and the JS files at the same time so it will
208         // hopefully be loaded when the JS will generate the settings content.
209         loadCss(getVar("static-root-path") + getVar("settings-css"));
210         loadScript(getVar("static-root-path") + getVar("settings-js"));
211     };
212
213     window.searchState = {
214         loadingText: "Loading search results...",
215         input: document.getElementsByClassName("search-input")[0],
216         outputElement: () => {
217             let el = document.getElementById("search");
218             if (!el) {
219                 el = document.createElement("section");
220                 el.id = "search";
221                 getNotDisplayedElem().appendChild(el);
222             }
223             return el;
224         },
225         title: document.title,
226         titleBeforeSearch: document.title,
227         timeout: null,
228         // On the search screen, so you remain on the last tab you opened.
229         //
230         // 0 for "In Names"
231         // 1 for "In Parameters"
232         // 2 for "In Return Types"
233         currentTab: 0,
234         // tab and back preserves the element that was focused.
235         focusedByTab: [null, null, null],
236         clearInputTimeout: () => {
237             if (searchState.timeout !== null) {
238                 clearTimeout(searchState.timeout);
239                 searchState.timeout = null;
240             }
241         },
242         isDisplayed: () => searchState.outputElement().parentElement.id === ALTERNATIVE_DISPLAY_ID,
243         // Sets the focus on the search bar at the top of the page
244         focus: () => {
245             searchState.input.focus();
246         },
247         // Removes the focus from the search bar.
248         defocus: () => {
249             searchState.input.blur();
250         },
251         showResults: search => {
252             if (search === null || typeof search === "undefined") {
253                 search = searchState.outputElement();
254             }
255             switchDisplayedElement(search);
256             searchState.mouseMovedAfterSearch = false;
257             document.title = searchState.title;
258         },
259         hideResults: () => {
260             switchDisplayedElement(null);
261             document.title = searchState.titleBeforeSearch;
262             // We also remove the query parameter from the URL.
263             if (browserSupportsHistoryApi()) {
264                 history.replaceState(null, window.currentCrate + " - Rust",
265                     getNakedUrl() + window.location.hash);
266             }
267         },
268         getQueryStringParams: () => {
269             const params = {};
270             window.location.search.substring(1).split("&").
271                 map(s => {
272                     const pair = s.split("=");
273                     params[decodeURIComponent(pair[0])] =
274                         typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]);
275                 });
276             return params;
277         },
278         setup: () => {
279             const search_input = searchState.input;
280             if (!searchState.input) {
281                 return;
282             }
283             let searchLoaded = false;
284             function loadSearch() {
285                 if (!searchLoaded) {
286                     searchLoaded = true;
287                     loadScript(getVar("static-root-path") + getVar("search-js"));
288                     loadScript(resourcePath("search-index", ".js"));
289                 }
290             }
291
292             search_input.addEventListener("focus", () => {
293                 search_input.origPlaceholder = search_input.placeholder;
294                 search_input.placeholder = "Type your search here.";
295                 loadSearch();
296             });
297
298             if (search_input.value !== "") {
299                 loadSearch();
300             }
301
302             const params = searchState.getQueryStringParams();
303             if (params.search !== undefined) {
304                 searchState.setLoadingSearch();
305                 loadSearch();
306             }
307         },
308         setLoadingSearch: () => {
309             const search = searchState.outputElement();
310             search.innerHTML = "<h3 class=\"search-loading\">" + searchState.loadingText + "</h3>";
311             searchState.showResults(search);
312         },
313     };
314
315     function getPageId() {
316         if (window.location.hash) {
317             const tmp = window.location.hash.replace(/^#/, "");
318             if (tmp.length > 0) {
319                 return tmp;
320             }
321         }
322         return null;
323     }
324
325     const toggleAllDocsId = "toggle-all-docs";
326     let savedHash = "";
327
328     function handleHashes(ev) {
329         if (ev !== null && searchState.isDisplayed() && ev.newURL) {
330             // This block occurs when clicking on an element in the navbar while
331             // in a search.
332             switchDisplayedElement(null);
333             const hash = ev.newURL.slice(ev.newURL.indexOf("#") + 1);
334             if (browserSupportsHistoryApi()) {
335                 // `window.location.search`` contains all the query parameters, not just `search`.
336                 history.replaceState(null, "",
337                     getNakedUrl() + window.location.search + "#" + hash);
338             }
339             const elem = document.getElementById(hash);
340             if (elem) {
341                 elem.scrollIntoView();
342             }
343         }
344         // This part is used in case an element is not visible.
345         if (savedHash !== window.location.hash) {
346             savedHash = window.location.hash;
347             if (savedHash.length === 0) {
348                 return;
349             }
350             expandSection(savedHash.slice(1)); // we remove the '#'
351         }
352     }
353
354     function onHashChange(ev) {
355         // If we're in mobile mode, we should hide the sidebar in any case.
356         hideSidebar();
357         handleHashes(ev);
358     }
359
360     function openParentDetails(elem) {
361         while (elem) {
362             if (elem.tagName === "DETAILS") {
363                 elem.open = true;
364             }
365             elem = elem.parentNode;
366         }
367     }
368
369     function expandSection(id) {
370         openParentDetails(document.getElementById(id));
371     }
372
373     function handleEscape(ev) {
374         searchState.clearInputTimeout();
375         switchDisplayedElement(null);
376         if (browserSupportsHistoryApi()) {
377             history.replaceState(null, window.currentCrate + " - Rust",
378                 getNakedUrl() + window.location.hash);
379         }
380         ev.preventDefault();
381         searchState.defocus();
382         window.hideAllModals(true); // true = reset focus for notable traits
383     }
384
385     function handleShortcut(ev) {
386         // Don't interfere with browser shortcuts
387         const disableShortcuts = getSettingValue("disable-shortcuts") === "true";
388         if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) {
389             return;
390         }
391
392         if (document.activeElement.tagName === "INPUT" &&
393             document.activeElement.type !== "checkbox" &&
394             document.activeElement.type !== "radio") {
395             switch (getVirtualKey(ev)) {
396             case "Escape":
397                 handleEscape(ev);
398                 break;
399             }
400         } else {
401             switch (getVirtualKey(ev)) {
402             case "Escape":
403                 handleEscape(ev);
404                 break;
405
406             case "s":
407             case "S":
408                 ev.preventDefault();
409                 searchState.focus();
410                 break;
411
412             case "+":
413                 ev.preventDefault();
414                 expandAllDocs();
415                 break;
416             case "-":
417                 ev.preventDefault();
418                 collapseAllDocs();
419                 break;
420
421             case "?":
422                 showHelp();
423                 break;
424
425             default:
426                 break;
427             }
428         }
429     }
430
431     document.addEventListener("keypress", handleShortcut);
432     document.addEventListener("keydown", handleShortcut);
433
434     function addSidebarItems() {
435         if (!window.SIDEBAR_ITEMS) {
436             return;
437         }
438         const sidebar = document.getElementsByClassName("sidebar-elems")[0];
439
440         /**
441          * Append to the sidebar a "block" of links - a heading along with a list (`<ul>`) of items.
442          *
443          * @param {string} shortty - A short type name, like "primitive", "mod", or "macro"
444          * @param {string} id - The HTML id of the corresponding section on the module page.
445          * @param {string} longty - A long, capitalized, plural name, like "Primitive Types",
446          *                          "Modules", or "Macros".
447          */
448         function block(shortty, id, longty) {
449             const filtered = window.SIDEBAR_ITEMS[shortty];
450             if (!filtered) {
451                 return;
452             }
453
454             const h3 = document.createElement("h3");
455             h3.innerHTML = `<a href="index.html#${id}">${longty}</a>`;
456             const ul = document.createElement("ul");
457             ul.className = "block " + shortty;
458
459             for (const item of filtered) {
460                 const name = item[0];
461                 const desc = item[1]; // can be null
462
463                 let path;
464                 if (shortty === "mod") {
465                     path = name + "/index.html";
466                 } else {
467                     path = shortty + "." + name + ".html";
468                 }
469                 const current_page = document.location.href.split("/").pop();
470                 const link = document.createElement("a");
471                 link.href = path;
472                 link.title = desc;
473                 if (path === current_page) {
474                     link.className = "current";
475                 }
476                 link.textContent = name;
477                 const li = document.createElement("li");
478                 li.appendChild(link);
479                 ul.appendChild(li);
480             }
481             sidebar.appendChild(h3);
482             sidebar.appendChild(ul);
483         }
484
485         if (sidebar) {
486             block("primitive", "primitives", "Primitive Types");
487             block("mod", "modules", "Modules");
488             block("macro", "macros", "Macros");
489             block("struct", "structs", "Structs");
490             block("enum", "enums", "Enums");
491             block("union", "unions", "Unions");
492             block("constant", "constants", "Constants");
493             block("static", "static", "Statics");
494             block("trait", "traits", "Traits");
495             block("fn", "functions", "Functions");
496             block("type", "types", "Type Definitions");
497             block("foreigntype", "foreign-types", "Foreign Types");
498             block("keyword", "keywords", "Keywords");
499             block("traitalias", "trait-aliases", "Trait Aliases");
500         }
501     }
502
503     window.register_implementors = imp => {
504         const implementors = document.getElementById("implementors-list");
505         const synthetic_implementors = document.getElementById("synthetic-implementors-list");
506         const inlined_types = new Set();
507
508         const TEXT_IDX = 0;
509         const SYNTHETIC_IDX = 1;
510         const TYPES_IDX = 2;
511
512         if (synthetic_implementors) {
513             // This `inlined_types` variable is used to avoid having the same implementation
514             // showing up twice. For example "String" in the "Sync" doc page.
515             //
516             // By the way, this is only used by and useful for traits implemented automatically
517             // (like "Send" and "Sync").
518             onEachLazy(synthetic_implementors.getElementsByClassName("impl"), el => {
519                 const aliases = el.getAttribute("data-aliases");
520                 if (!aliases) {
521                     return;
522                 }
523                 aliases.split(",").forEach(alias => {
524                     inlined_types.add(alias);
525                 });
526             });
527         }
528
529         let currentNbImpls = implementors.getElementsByClassName("impl").length;
530         const traitName = document.querySelector(".main-heading h1 > .trait").textContent;
531         const baseIdName = "impl-" + traitName + "-";
532         const libs = Object.getOwnPropertyNames(imp);
533         // We don't want to include impls from this JS file, when the HTML already has them.
534         // The current crate should always be ignored. Other crates that should also be
535         // ignored are included in the attribute `data-ignore-extern-crates`.
536         const script = document
537             .querySelector("script[data-ignore-extern-crates]");
538         const ignoreExternCrates = script ? script.getAttribute("data-ignore-extern-crates") : "";
539         for (const lib of libs) {
540             if (lib === window.currentCrate || ignoreExternCrates.indexOf(lib) !== -1) {
541                 continue;
542             }
543             const structs = imp[lib];
544
545             struct_loop:
546             for (const struct of structs) {
547                 const list = struct[SYNTHETIC_IDX] ? synthetic_implementors : implementors;
548
549                 // The types list is only used for synthetic impls.
550                 // If this changes, `main.js` and `write_shared.rs` both need changed.
551                 if (struct[SYNTHETIC_IDX]) {
552                     for (const struct_type of struct[TYPES_IDX]) {
553                         if (inlined_types.has(struct_type)) {
554                             continue struct_loop;
555                         }
556                         inlined_types.add(struct_type);
557                     }
558                 }
559
560                 const code = document.createElement("h3");
561                 code.innerHTML = struct[TEXT_IDX];
562                 addClass(code, "code-header");
563
564                 onEachLazy(code.getElementsByTagName("a"), elem => {
565                     const href = elem.getAttribute("href");
566
567                     if (href && !/^(?:[a-z+]+:)?\/\//.test(href)) {
568                         elem.setAttribute("href", window.rootPath + href);
569                     }
570                 });
571
572                 const currentId = baseIdName + currentNbImpls;
573                 const anchor = document.createElement("a");
574                 anchor.href = "#" + currentId;
575                 addClass(anchor, "anchor");
576
577                 const display = document.createElement("div");
578                 display.id = currentId;
579                 addClass(display, "impl");
580                 display.appendChild(anchor);
581                 display.appendChild(code);
582                 list.appendChild(display);
583                 currentNbImpls += 1;
584             }
585         }
586     };
587     if (window.pending_implementors) {
588         window.register_implementors(window.pending_implementors);
589     }
590
591     function addSidebarCrates() {
592         if (!window.ALL_CRATES) {
593             return;
594         }
595         const sidebarElems = document.getElementsByClassName("sidebar-elems")[0];
596         if (!sidebarElems) {
597             return;
598         }
599         // Draw a convenient sidebar of known crates if we have a listing
600         const h3 = document.createElement("h3");
601         h3.innerHTML = "Crates";
602         const ul = document.createElement("ul");
603         ul.className = "block crate";
604
605         for (const crate of window.ALL_CRATES) {
606             const link = document.createElement("a");
607             link.href = window.rootPath + crate + "/index.html";
608             if (window.rootPath !== "./" && crate === window.currentCrate) {
609                 link.className = "current";
610             }
611             link.textContent = crate;
612
613             const li = document.createElement("li");
614             li.appendChild(link);
615             ul.appendChild(li);
616         }
617         sidebarElems.appendChild(h3);
618         sidebarElems.appendChild(ul);
619     }
620
621     function expandAllDocs() {
622         const innerToggle = document.getElementById(toggleAllDocsId);
623         removeClass(innerToggle, "will-expand");
624         onEachLazy(document.getElementsByClassName("toggle"), e => {
625             if (!hasClass(e, "type-contents-toggle") && !hasClass(e, "more-examples-toggle")) {
626                 e.open = true;
627             }
628         });
629         innerToggle.title = "collapse all docs";
630         innerToggle.children[0].innerText = "\u2212"; // "\u2212" is "−" minus sign
631     }
632
633     function collapseAllDocs() {
634         const innerToggle = document.getElementById(toggleAllDocsId);
635         addClass(innerToggle, "will-expand");
636         onEachLazy(document.getElementsByClassName("toggle"), e => {
637             if (e.parentNode.id !== "implementations-list" ||
638                 (!hasClass(e, "implementors-toggle") &&
639                  !hasClass(e, "type-contents-toggle"))
640             ) {
641                 e.open = false;
642             }
643         });
644         innerToggle.title = "expand all docs";
645         innerToggle.children[0].innerText = "+";
646     }
647
648     function toggleAllDocs() {
649         const innerToggle = document.getElementById(toggleAllDocsId);
650         if (!innerToggle) {
651             return;
652         }
653         if (hasClass(innerToggle, "will-expand")) {
654             expandAllDocs();
655         } else {
656             collapseAllDocs();
657         }
658     }
659
660     (function() {
661         const toggles = document.getElementById(toggleAllDocsId);
662         if (toggles) {
663             toggles.onclick = toggleAllDocs;
664         }
665
666         const hideMethodDocs = getSettingValue("auto-hide-method-docs") === "true";
667         const hideImplementations = getSettingValue("auto-hide-trait-implementations") === "true";
668         const hideLargeItemContents = getSettingValue("auto-hide-large-items") !== "false";
669
670         function setImplementorsTogglesOpen(id, open) {
671             const list = document.getElementById(id);
672             if (list !== null) {
673                 onEachLazy(list.getElementsByClassName("implementors-toggle"), e => {
674                     e.open = open;
675                 });
676             }
677         }
678
679         if (hideImplementations) {
680             setImplementorsTogglesOpen("trait-implementations-list", false);
681             setImplementorsTogglesOpen("blanket-implementations-list", false);
682         }
683
684         onEachLazy(document.getElementsByClassName("toggle"), e => {
685             if (!hideLargeItemContents && hasClass(e, "type-contents-toggle")) {
686                 e.open = true;
687             }
688             if (hideMethodDocs && hasClass(e, "method-toggle")) {
689                 e.open = false;
690             }
691
692         });
693
694         const pageId = getPageId();
695         if (pageId !== null) {
696             expandSection(pageId);
697         }
698     }());
699
700     window.rustdoc_add_line_numbers_to_examples = () => {
701         onEachLazy(document.getElementsByClassName("rust-example-rendered"), x => {
702             const parent = x.parentNode;
703             const line_numbers = parent.querySelectorAll(".example-line-numbers");
704             if (line_numbers.length > 0) {
705                 return;
706             }
707             const count = x.textContent.split("\n").length;
708             const elems = [];
709             for (let i = 0; i < count; ++i) {
710                 elems.push(i + 1);
711             }
712             const node = document.createElement("pre");
713             addClass(node, "example-line-numbers");
714             node.innerHTML = elems.join("\n");
715             parent.insertBefore(node, x);
716         });
717     };
718
719     window.rustdoc_remove_line_numbers_from_examples = () => {
720         onEachLazy(document.getElementsByClassName("rust-example-rendered"), x => {
721             const parent = x.parentNode;
722             const line_numbers = parent.querySelectorAll(".example-line-numbers");
723             for (const node of line_numbers) {
724                 parent.removeChild(node);
725             }
726         });
727     };
728
729     if (getSettingValue("line-numbers") === "true") {
730         window.rustdoc_add_line_numbers_to_examples();
731     }
732
733     let oldSidebarScrollPosition = null;
734
735     // Scroll locking used both here and in source-script.js
736
737     window.rustdocMobileScrollLock = function() {
738         const mobile_topbar = document.querySelector(".mobile-topbar");
739         if (window.innerWidth <= window.RUSTDOC_MOBILE_BREAKPOINT) {
740             // This is to keep the scroll position on mobile.
741             oldSidebarScrollPosition = window.scrollY;
742             document.body.style.width = `${document.body.offsetWidth}px`;
743             document.body.style.position = "fixed";
744             document.body.style.top = `-${oldSidebarScrollPosition}px`;
745             if (mobile_topbar) {
746                 mobile_topbar.style.top = `${oldSidebarScrollPosition}px`;
747                 mobile_topbar.style.position = "relative";
748             }
749         } else {
750             oldSidebarScrollPosition = null;
751         }
752     };
753
754     window.rustdocMobileScrollUnlock = function() {
755         const mobile_topbar = document.querySelector(".mobile-topbar");
756         if (oldSidebarScrollPosition !== null) {
757             // This is to keep the scroll position on mobile.
758             document.body.style.width = "";
759             document.body.style.position = "";
760             document.body.style.top = "";
761             if (mobile_topbar) {
762                 mobile_topbar.style.top = "";
763                 mobile_topbar.style.position = "";
764             }
765             // The scroll position is lost when resetting the style, hence why we store it in
766             // `oldSidebarScrollPosition`.
767             window.scrollTo(0, oldSidebarScrollPosition);
768             oldSidebarScrollPosition = null;
769         }
770     };
771
772     function showSidebar() {
773         window.hideAllModals(false);
774         window.rustdocMobileScrollLock();
775         const sidebar = document.getElementsByClassName("sidebar")[0];
776         addClass(sidebar, "shown");
777     }
778
779     function hideSidebar() {
780         window.rustdocMobileScrollUnlock();
781         const sidebar = document.getElementsByClassName("sidebar")[0];
782         removeClass(sidebar, "shown");
783     }
784
785     window.addEventListener("resize", () => {
786         if (window.innerWidth > window.RUSTDOC_MOBILE_BREAKPOINT &&
787             oldSidebarScrollPosition !== null) {
788             // If the user opens the sidebar in "mobile" mode, and then grows the browser window,
789             // we need to switch away from mobile mode and make the main content area scrollable.
790             hideSidebar();
791         }
792         if (window.CURRENT_NOTABLE_ELEMENT) {
793             // As a workaround to the behavior of `contains: layout` used in doc togglers, the
794             // notable traits popup is positioned using javascript.
795             //
796             // This means when the window is resized, we need to redo the layout.
797             const base = window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE;
798             const force_visible = base.NOTABLE_FORCE_VISIBLE;
799             hideNotable(false);
800             if (force_visible) {
801                 showNotable(base);
802                 base.NOTABLE_FORCE_VISIBLE = true;
803             }
804         }
805     });
806
807     const mainElem = document.getElementById(MAIN_ID);
808     if (mainElem) {
809         mainElem.addEventListener("click", hideSidebar);
810     }
811
812     onEachLazy(document.querySelectorAll("a[href^='#']"), el => {
813         // For clicks on internal links (<A> tags with a hash property), we expand the section we're
814         // jumping to *before* jumping there. We can't do this in onHashChange, because it changes
815         // the height of the document so we wind up scrolled to the wrong place.
816         el.addEventListener("click", () => {
817             expandSection(el.hash.slice(1));
818             hideSidebar();
819         });
820     });
821
822     onEachLazy(document.querySelectorAll(".toggle > summary:not(.hideme)"), el => {
823         el.addEventListener("click", e => {
824             if (e.target.tagName !== "SUMMARY" && e.target.tagName !== "A") {
825                 e.preventDefault();
826             }
827         });
828     });
829
830     function showNotable(e) {
831         if (!window.NOTABLE_TRAITS) {
832             const data = document.getElementById("notable-traits-data");
833             if (data) {
834                 window.NOTABLE_TRAITS = JSON.parse(data.innerText);
835             } else {
836                 throw new Error("showNotable() called on page without any notable traits!");
837             }
838         }
839         if (window.CURRENT_NOTABLE_ELEMENT && window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE === e) {
840             // Make this function idempotent.
841             return;
842         }
843         window.hideAllModals(false);
844         const ty = e.getAttribute("data-ty");
845         const wrapper = document.createElement("div");
846         wrapper.innerHTML = "<div class=\"content\">" + window.NOTABLE_TRAITS[ty] + "</div>";
847         wrapper.className = "notable popover";
848         const focusCatcher = document.createElement("div");
849         focusCatcher.setAttribute("tabindex", "0");
850         focusCatcher.onfocus = hideNotable;
851         wrapper.appendChild(focusCatcher);
852         const pos = e.getBoundingClientRect();
853         // 5px overlap so that the mouse can easily travel from place to place
854         wrapper.style.top = (pos.top + window.scrollY + pos.height) + "px";
855         wrapper.style.left = 0;
856         wrapper.style.right = "auto";
857         wrapper.style.visibility = "hidden";
858         const body = document.getElementsByTagName("body")[0];
859         body.appendChild(wrapper);
860         const wrapperPos = wrapper.getBoundingClientRect();
861         // offset so that the arrow points at the center of the "(i)"
862         const finalPos = pos.left + window.scrollX - wrapperPos.width + 24;
863         if (finalPos > 0) {
864             wrapper.style.left = finalPos + "px";
865         } else {
866             wrapper.style.setProperty(
867                 "--popover-arrow-offset",
868                 (wrapperPos.right - pos.right + 4) + "px"
869             );
870         }
871         wrapper.style.visibility = "";
872         window.CURRENT_NOTABLE_ELEMENT = wrapper;
873         window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE = e;
874         wrapper.onpointerleave = function(ev) {
875             // If this is a synthetic touch event, ignore it. A click event will be along shortly.
876             if (ev.pointerType !== "mouse") {
877                 return;
878             }
879             if (!e.NOTABLE_FORCE_VISIBLE && !elemIsInParent(event.relatedTarget, e)) {
880                 hideNotable(true);
881             }
882         };
883     }
884
885     function notableBlurHandler(event) {
886         if (window.CURRENT_NOTABLE_ELEMENT &&
887             !elemIsInParent(document.activeElement, window.CURRENT_NOTABLE_ELEMENT) &&
888             !elemIsInParent(event.relatedTarget, window.CURRENT_NOTABLE_ELEMENT) &&
889             !elemIsInParent(document.activeElement, window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE) &&
890             !elemIsInParent(event.relatedTarget, window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE)
891         ) {
892             // Work around a difference in the focus behaviour between Firefox, Chrome, and Safari.
893             // When I click the button on an already-opened notable trait popover, Safari
894             // hides the popover and then immediately shows it again, while everyone else hides it
895             // and it stays hidden.
896             //
897             // To work around this, make sure the click finishes being dispatched before
898             // hiding the popover. Since `hideNotable()` is idempotent, this makes Safari behave
899             // consistently with the other two.
900             setTimeout(() => hideNotable(false), 0);
901         }
902     }
903
904     function hideNotable(focus) {
905         if (window.CURRENT_NOTABLE_ELEMENT) {
906             if (window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.NOTABLE_FORCE_VISIBLE) {
907                 if (focus) {
908                     window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.focus();
909                 }
910                 window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.NOTABLE_FORCE_VISIBLE = false;
911             }
912             const body = document.getElementsByTagName("body")[0];
913             body.removeChild(window.CURRENT_NOTABLE_ELEMENT);
914             window.CURRENT_NOTABLE_ELEMENT = null;
915         }
916     }
917
918     onEachLazy(document.getElementsByClassName("notable-traits"), e => {
919         e.onclick = function() {
920             this.NOTABLE_FORCE_VISIBLE = this.NOTABLE_FORCE_VISIBLE ? false : true;
921             if (window.CURRENT_NOTABLE_ELEMENT && !this.NOTABLE_FORCE_VISIBLE) {
922                 hideNotable(true);
923             } else {
924                 showNotable(this);
925                 window.CURRENT_NOTABLE_ELEMENT.setAttribute("tabindex", "0");
926                 window.CURRENT_NOTABLE_ELEMENT.focus();
927                 window.CURRENT_NOTABLE_ELEMENT.onblur = notableBlurHandler;
928             }
929             return false;
930         };
931         e.onpointerenter = function(ev) {
932             // If this is a synthetic touch event, ignore it. A click event will be along shortly.
933             if (ev.pointerType !== "mouse") {
934                 return;
935             }
936             showNotable(this);
937         };
938         e.onpointerleave = function(ev) {
939             // If this is a synthetic touch event, ignore it. A click event will be along shortly.
940             if (ev.pointerType !== "mouse") {
941                 return;
942             }
943             if (!this.NOTABLE_FORCE_VISIBLE &&
944                 !elemIsInParent(ev.relatedTarget, window.CURRENT_NOTABLE_ELEMENT)) {
945                 hideNotable(true);
946             }
947         };
948     });
949
950     const sidebar_menu_toggle = document.getElementsByClassName("sidebar-menu-toggle")[0];
951     if (sidebar_menu_toggle) {
952         sidebar_menu_toggle.addEventListener("click", () => {
953             const sidebar = document.getElementsByClassName("sidebar")[0];
954             if (!hasClass(sidebar, "shown")) {
955                 showSidebar();
956             } else {
957                 hideSidebar();
958             }
959         });
960     }
961
962     function helpBlurHandler(event) {
963         blurHandler(event, getHelpButton(), window.hidePopoverMenus);
964     }
965
966     function buildHelpMenu() {
967         const book_info = document.createElement("span");
968         book_info.className = "top";
969         book_info.innerHTML = "You can find more information in \
970             <a href=\"https://doc.rust-lang.org/rustdoc/\">the rustdoc book</a>.";
971
972         const shortcuts = [
973             ["?", "Show this help dialog"],
974             ["S", "Focus the search field"],
975             ["↑", "Move up in search results"],
976             ["↓", "Move down in search results"],
977             ["← / →", "Switch result tab (when results focused)"],
978             ["&#9166;", "Go to active search result"],
979             ["+", "Expand all sections"],
980             ["-", "Collapse all sections"],
981         ].map(x => "<dt>" +
982             x[0].split(" ")
983                 .map((y, index) => ((index & 1) === 0 ? "<kbd>" + y + "</kbd>" : " " + y + " "))
984                 .join("") + "</dt><dd>" + x[1] + "</dd>").join("");
985         const div_shortcuts = document.createElement("div");
986         addClass(div_shortcuts, "shortcuts");
987         div_shortcuts.innerHTML = "<h2>Keyboard Shortcuts</h2><dl>" + shortcuts + "</dl></div>";
988
989         const infos = [
990             "Prefix searches with a type followed by a colon (e.g., <code>fn:</code>) to \
991              restrict the search to a given item kind.",
992             "Accepted kinds are: <code>fn</code>, <code>mod</code>, <code>struct</code>, \
993              <code>enum</code>, <code>trait</code>, <code>type</code>, <code>macro</code>, \
994              and <code>const</code>.",
995             "Search functions by type signature (e.g., <code>vec -&gt; usize</code> or \
996              <code>-&gt; vec</code>)",
997             "Search multiple things at once by splitting your query with comma (e.g., \
998              <code>str,u8</code> or <code>String,struct:Vec,test</code>)",
999             "You can look for items with an exact name by putting double quotes around \
1000              your request: <code>\"string\"</code>",
1001             "Look for items inside another one by searching for a path: <code>vec::Vec</code>",
1002         ].map(x => "<p>" + x + "</p>").join("");
1003         const div_infos = document.createElement("div");
1004         addClass(div_infos, "infos");
1005         div_infos.innerHTML = "<h2>Search Tricks</h2>" + infos;
1006
1007         const rustdoc_version = document.createElement("span");
1008         rustdoc_version.className = "bottom";
1009         const rustdoc_version_code = document.createElement("code");
1010         rustdoc_version_code.innerText = "rustdoc " + getVar("rustdoc-version");
1011         rustdoc_version.appendChild(rustdoc_version_code);
1012
1013         const container = document.createElement("div");
1014         if (!isHelpPage) {
1015             container.className = "popover";
1016         }
1017         container.id = "help";
1018         container.style.display = "none";
1019
1020         const side_by_side = document.createElement("div");
1021         side_by_side.className = "side-by-side";
1022         side_by_side.appendChild(div_shortcuts);
1023         side_by_side.appendChild(div_infos);
1024
1025         container.appendChild(book_info);
1026         container.appendChild(side_by_side);
1027         container.appendChild(rustdoc_version);
1028
1029         if (isHelpPage) {
1030             const help_section = document.createElement("section");
1031             help_section.appendChild(container);
1032             document.getElementById("main-content").appendChild(help_section);
1033             container.style.display = "block";
1034         } else {
1035             const help_button = getHelpButton();
1036             help_button.appendChild(container);
1037
1038             container.onblur = helpBlurHandler;
1039             help_button.onblur = helpBlurHandler;
1040             help_button.children[0].onblur = helpBlurHandler;
1041         }
1042
1043         return container;
1044     }
1045
1046     /**
1047      * Hide popover menus, notable trait tooltips, and the sidebar (if applicable).
1048      *
1049      * Pass "true" to reset focus for notable traits.
1050      */
1051     window.hideAllModals = function(switchFocus) {
1052         hideSidebar();
1053         window.hidePopoverMenus();
1054         hideNotable(switchFocus);
1055     };
1056
1057     /**
1058      * Hide all the popover menus.
1059      */
1060     window.hidePopoverMenus = function() {
1061         onEachLazy(document.querySelectorAll(".search-form .popover"), elem => {
1062             elem.style.display = "none";
1063         });
1064     };
1065
1066     /**
1067      * Returns the help menu element (not the button).
1068      *
1069      * @param {boolean} buildNeeded - If this argument is `false`, the help menu element won't be
1070      *                                built if it doesn't exist.
1071      *
1072      * @return {HTMLElement}
1073      */
1074     function getHelpMenu(buildNeeded) {
1075         let menu = getHelpButton().querySelector(".popover");
1076         if (!menu && buildNeeded) {
1077             menu = buildHelpMenu();
1078         }
1079         return menu;
1080     }
1081
1082     /**
1083      * Show the help popup menu.
1084      */
1085     function showHelp() {
1086         // Prevent `blur` events from being dispatched as a result of closing
1087         // other modals.
1088         getHelpButton().querySelector("a").focus();
1089         const menu = getHelpMenu(true);
1090         if (menu.style.display === "none") {
1091             window.hideAllModals();
1092             menu.style.display = "";
1093         }
1094     }
1095
1096     if (isHelpPage) {
1097         showHelp();
1098         document.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click", event => {
1099             // Already on the help page, make help button a no-op.
1100             const target = event.target;
1101             if (target.tagName !== "A" ||
1102                 target.parentElement.id !== HELP_BUTTON_ID ||
1103                 event.ctrlKey ||
1104                 event.altKey ||
1105                 event.metaKey) {
1106                 return;
1107             }
1108             event.preventDefault();
1109         });
1110     } else {
1111         document.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click", event => {
1112             // By default, have help button open docs in a popover.
1113             // If user clicks with a moderator, though, use default browser behavior,
1114             // probably opening in a new window or tab.
1115             const target = event.target;
1116             if (target.tagName !== "A" ||
1117                 target.parentElement.id !== HELP_BUTTON_ID ||
1118                 event.ctrlKey ||
1119                 event.altKey ||
1120                 event.metaKey) {
1121                 return;
1122             }
1123             event.preventDefault();
1124             const menu = getHelpMenu(true);
1125             const shouldShowHelp = menu.style.display === "none";
1126             if (shouldShowHelp) {
1127                 showHelp();
1128             } else {
1129                 window.hidePopoverMenus();
1130             }
1131         });
1132     }
1133
1134     setMobileTopbar();
1135     addSidebarItems();
1136     addSidebarCrates();
1137     onHashChange(null);
1138     window.addEventListener("hashchange", onHashChange);
1139     searchState.setup();
1140 }());
1141
1142 (function() {
1143     let reset_button_timeout = null;
1144
1145     const but = document.getElementById("copy-path");
1146     if (!but) {
1147         return;
1148     }
1149     but.onclick = () => {
1150         const parent = but.parentElement;
1151         const path = [];
1152
1153         onEach(parent.childNodes, child => {
1154             if (child.tagName === "A") {
1155                 path.push(child.textContent);
1156             }
1157         });
1158
1159         const el = document.createElement("textarea");
1160         el.value = path.join("::");
1161         el.setAttribute("readonly", "");
1162         // To not make it appear on the screen.
1163         el.style.position = "absolute";
1164         el.style.left = "-9999px";
1165
1166         document.body.appendChild(el);
1167         el.select();
1168         document.execCommand("copy");
1169         document.body.removeChild(el);
1170
1171         // There is always one children, but multiple childNodes.
1172         but.children[0].style.display = "none";
1173
1174         let tmp;
1175         if (but.childNodes.length < 2) {
1176             tmp = document.createTextNode("✓");
1177             but.appendChild(tmp);
1178         } else {
1179             onEachLazy(but.childNodes, e => {
1180                 if (e.nodeType === Node.TEXT_NODE) {
1181                     tmp = e;
1182                     return true;
1183                 }
1184             });
1185             tmp.textContent = "✓";
1186         }
1187
1188         if (reset_button_timeout !== null) {
1189             window.clearTimeout(reset_button_timeout);
1190         }
1191
1192         function reset_button() {
1193             tmp.textContent = "";
1194             reset_button_timeout = null;
1195             but.children[0].style.display = "";
1196         }
1197
1198         reset_button_timeout = window.setTimeout(reset_button, 1000);
1199     };
1200 }());