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