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