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