1 // Local js definitions:
2 /* global addClass, getSettingValue, hasClass, searchState */
3 /* global onEach, onEachLazy, removeClass */
7 // Get a value from the rustdoc-vars div, which is used to convey data from
8 // Rust to the JS. If there is no such element, return null.
9 function getVar(name) {
10 const el = document.getElementById("rustdoc-vars");
12 return el.attributes["data-" + name].value;
18 // Given a basename (e.g. "storage") and an extension (e.g. ".js"), return a URL
19 // for a resource under the root-path, with the resource-suffix.
20 function resourcePath(basename, extension) {
21 return getVar("root-path") + basename + getVar("resource-suffix") + extension;
25 addClass(document.getElementById(MAIN_ID), "hidden");
29 removeClass(document.getElementById(MAIN_ID), "hidden");
32 function elemIsInParent(elem, parent) {
33 while (elem && elem !== document.body) {
34 if (elem === parent) {
37 elem = elem.parentElement;
42 function blurHandler(event, parentElem, hideCallback) {
43 if (!elemIsInParent(document.activeElement, parentElem) &&
44 !elemIsInParent(event.relatedTarget, parentElem)
50 window.rootPath = getVar("root-path");
51 window.currentCrate = getVar("current-crate");
53 function setMobileTopbar() {
54 // FIXME: It would be nicer to generate this text content directly in HTML,
55 // but with the current code it's hard to get the right information in the right place.
56 const mobileLocationTitle = document.querySelector(".mobile-topbar h2");
57 const locationTitle = document.querySelector(".sidebar h2.location");
58 if (mobileLocationTitle && locationTitle) {
59 mobileLocationTitle.innerHTML = locationTitle.innerHTML;
63 // Gets the human-readable string for the virtual-key code of the
64 // given KeyboardEvent, ev.
66 // This function is meant as a polyfill for KeyboardEvent#key,
67 // since it is not supported in IE 11 or Chrome for Android. We also test for
68 // KeyboardEvent#keyCode because the handleShortcut handler is
69 // also registered for the keydown event, because Blink doesn't fire
70 // keypress on hitting the Escape key.
72 // So I guess you could say things are getting pretty interoperable.
73 function getVirtualKey(ev) {
74 if ("key" in ev && typeof ev.key !== "undefined") {
78 const c = ev.charCode || ev.keyCode;
82 return String.fromCharCode(c);
85 const MAIN_ID = "main-content";
86 const SETTINGS_BUTTON_ID = "settings-menu";
87 const ALTERNATIVE_DISPLAY_ID = "alternative-display";
88 const NOT_DISPLAYED_ID = "not-displayed";
89 const HELP_BUTTON_ID = "help-button";
91 function getSettingsButton() {
92 return document.getElementById(SETTINGS_BUTTON_ID);
95 function getHelpButton() {
96 return document.getElementById(HELP_BUTTON_ID);
99 // Returns the current URL without any query parameter or hash.
100 function getNakedUrl() {
101 return window.location.href.split("?")[0].split("#")[0];
105 * This function inserts `newNode` after `referenceNode`. It doesn't work if `referenceNode`
106 * doesn't have a parent node.
108 * @param {HTMLElement} newNode
109 * @param {HTMLElement} referenceNode
111 function insertAfter(newNode, referenceNode) {
112 referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
116 * This function creates a new `<section>` with the given `id` and `classes` if it doesn't already
119 * More information about this in `switchDisplayedElement` documentation.
122 * @param {string} classes
124 function getOrCreateSection(id, classes) {
125 let el = document.getElementById(id);
128 el = document.createElement("section");
130 el.className = classes;
131 insertAfter(el, document.getElementById(MAIN_ID));
137 * Returns the `<section>` element which contains the displayed element.
139 * @return {HTMLElement}
141 function getAlternativeDisplayElem() {
142 return getOrCreateSection(ALTERNATIVE_DISPLAY_ID, "content hidden");
146 * Returns the `<section>` element which contains the not-displayed elements.
148 * @return {HTMLElement}
150 function getNotDisplayedElem() {
151 return getOrCreateSection(NOT_DISPLAYED_ID, "hidden");
155 * To nicely switch between displayed "extra" elements (such as search results or settings menu)
156 * and to alternate between the displayed and not displayed elements, we hold them in two different
157 * `<section>` elements. They work in pair: one holds the hidden elements while the other
158 * contains the displayed element (there can be only one at the same time!). So basically, we switch
159 * elements between the two `<section>` elements.
161 * @param {HTMLElement} elemToDisplay
163 function switchDisplayedElement(elemToDisplay) {
164 const el = getAlternativeDisplayElem();
166 if (el.children.length > 0) {
167 getNotDisplayedElem().appendChild(el.firstElementChild);
169 if (elemToDisplay === null) {
170 addClass(el, "hidden");
174 el.appendChild(elemToDisplay);
176 removeClass(el, "hidden");
179 function browserSupportsHistoryApi() {
180 return window.history && typeof window.history.pushState === "function";
183 function loadCss(cssUrl) {
184 const link = document.createElement("link");
186 link.rel = "stylesheet";
187 document.getElementsByTagName("head")[0].appendChild(link);
191 const isHelpPage = window.location.pathname.endsWith("/help.html");
193 function loadScript(url) {
194 const script = document.createElement("script");
196 document.head.append(script);
199 getSettingsButton().onclick = event => {
200 if (event.ctrlKey || event.altKey || event.metaKey) {
203 window.hideAllModals(false);
204 addClass(getSettingsButton(), "rotate");
205 event.preventDefault();
206 // Sending request for the CSS and the JS files at the same time so it will
207 // hopefully be loaded when the JS will generate the settings content.
208 loadCss(getVar("static-root-path") + getVar("settings-css"));
209 loadScript(getVar("static-root-path") + getVar("settings-js"));
212 window.searchState = {
213 loadingText: "Loading search results...",
214 input: document.getElementsByClassName("search-input")[0],
215 outputElement: () => {
216 let el = document.getElementById("search");
218 el = document.createElement("section");
220 getNotDisplayedElem().appendChild(el);
224 title: document.title,
225 titleBeforeSearch: document.title,
227 // On the search screen, so you remain on the last tab you opened.
230 // 1 for "In Parameters"
231 // 2 for "In Return Types"
233 // tab and back preserves the element that was focused.
234 focusedByTab: [null, null, null],
235 clearInputTimeout: () => {
236 if (searchState.timeout !== null) {
237 clearTimeout(searchState.timeout);
238 searchState.timeout = null;
241 isDisplayed: () => searchState.outputElement().parentElement.id === ALTERNATIVE_DISPLAY_ID,
242 // Sets the focus on the search bar at the top of the page
244 searchState.input.focus();
246 // Removes the focus from the search bar.
248 searchState.input.blur();
250 showResults: search => {
251 if (search === null || typeof search === "undefined") {
252 search = searchState.outputElement();
254 switchDisplayedElement(search);
255 searchState.mouseMovedAfterSearch = false;
256 document.title = searchState.title;
259 switchDisplayedElement(null);
260 document.title = searchState.titleBeforeSearch;
261 // We also remove the query parameter from the URL.
262 if (browserSupportsHistoryApi()) {
263 history.replaceState(null, window.currentCrate + " - Rust",
264 getNakedUrl() + window.location.hash);
267 getQueryStringParams: () => {
269 window.location.search.substring(1).split("&").
271 const pair = s.split("=");
272 params[decodeURIComponent(pair[0])] =
273 typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]);
278 const search_input = searchState.input;
279 if (!searchState.input) {
282 let searchLoaded = false;
283 function loadSearch() {
286 loadScript(getVar("static-root-path") + getVar("search-js"));
287 loadScript(resourcePath("search-index", ".js"));
291 search_input.addEventListener("focus", () => {
292 search_input.origPlaceholder = search_input.placeholder;
293 search_input.placeholder = "Type your search here.";
297 if (search_input.value !== "") {
301 const params = searchState.getQueryStringParams();
302 if (params.search !== undefined) {
303 searchState.setLoadingSearch();
307 setLoadingSearch: () => {
308 const search = searchState.outputElement();
309 search.innerHTML = "<h3 class=\"search-loading\">" + searchState.loadingText + "</h3>";
310 searchState.showResults(search);
314 function getPageId() {
315 if (window.location.hash) {
316 const tmp = window.location.hash.replace(/^#/, "");
317 if (tmp.length > 0) {
324 const toggleAllDocsId = "toggle-all-docs";
327 function handleHashes(ev) {
328 if (ev !== null && searchState.isDisplayed() && ev.newURL) {
329 // This block occurs when clicking on an element in the navbar while
331 switchDisplayedElement(null);
332 const hash = ev.newURL.slice(ev.newURL.indexOf("#") + 1);
333 if (browserSupportsHistoryApi()) {
334 // `window.location.search`` contains all the query parameters, not just `search`.
335 history.replaceState(null, "",
336 getNakedUrl() + window.location.search + "#" + hash);
338 const elem = document.getElementById(hash);
340 elem.scrollIntoView();
343 // This part is used in case an element is not visible.
344 if (savedHash !== window.location.hash) {
345 savedHash = window.location.hash;
346 if (savedHash.length === 0) {
349 expandSection(savedHash.slice(1)); // we remove the '#'
353 function onHashChange(ev) {
354 // If we're in mobile mode, we should hide the sidebar in any case.
359 function openParentDetails(elem) {
361 if (elem.tagName === "DETAILS") {
364 elem = elem.parentNode;
368 function expandSection(id) {
369 openParentDetails(document.getElementById(id));
372 function handleEscape(ev) {
373 searchState.clearInputTimeout();
374 switchDisplayedElement(null);
375 if (browserSupportsHistoryApi()) {
376 history.replaceState(null, window.currentCrate + " - Rust",
377 getNakedUrl() + window.location.hash);
380 searchState.defocus();
381 window.hideAllModals(true); // true = reset focus for notable traits
384 function handleShortcut(ev) {
385 // Don't interfere with browser shortcuts
386 const disableShortcuts = getSettingValue("disable-shortcuts") === "true";
387 if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) {
391 if (document.activeElement.tagName === "INPUT" &&
392 document.activeElement.type !== "checkbox" &&
393 document.activeElement.type !== "radio") {
394 switch (getVirtualKey(ev)) {
400 switch (getVirtualKey(ev)) {
430 document.addEventListener("keypress", handleShortcut);
431 document.addEventListener("keydown", handleShortcut);
433 function addSidebarItems() {
434 if (!window.SIDEBAR_ITEMS) {
437 const sidebar = document.getElementsByClassName("sidebar-elems")[0];
440 * Append to the sidebar a "block" of links - a heading along with a list (`<ul>`) of items.
442 * @param {string} shortty - A short type name, like "primitive", "mod", or "macro"
443 * @param {string} id - The HTML id of the corresponding section on the module page.
444 * @param {string} longty - A long, capitalized, plural name, like "Primitive Types",
445 * "Modules", or "Macros".
447 function block(shortty, id, longty) {
448 const filtered = window.SIDEBAR_ITEMS[shortty];
453 const h3 = document.createElement("h3");
454 h3.innerHTML = `<a href="index.html#${id}">${longty}</a>`;
455 const ul = document.createElement("ul");
456 ul.className = "block " + shortty;
458 for (const item of filtered) {
459 const name = item[0];
460 const desc = item[1]; // can be null
463 if (shortty === "mod") {
464 path = name + "/index.html";
466 path = shortty + "." + name + ".html";
468 const current_page = document.location.href.split("/").pop();
469 const link = document.createElement("a");
472 if (path === current_page) {
473 link.className = "current";
475 link.textContent = name;
476 const li = document.createElement("li");
477 li.appendChild(link);
480 sidebar.appendChild(h3);
481 sidebar.appendChild(ul);
485 block("primitive", "primitives", "Primitive Types");
486 block("mod", "modules", "Modules");
487 block("macro", "macros", "Macros");
488 block("struct", "structs", "Structs");
489 block("enum", "enums", "Enums");
490 block("union", "unions", "Unions");
491 block("constant", "constants", "Constants");
492 block("static", "static", "Statics");
493 block("trait", "traits", "Traits");
494 block("fn", "functions", "Functions");
495 block("type", "types", "Type Definitions");
496 block("foreigntype", "foreign-types", "Foreign Types");
497 block("keyword", "keywords", "Keywords");
498 block("traitalias", "trait-aliases", "Trait Aliases");
502 window.register_implementors = imp => {
503 const implementors = document.getElementById("implementors-list");
504 const synthetic_implementors = document.getElementById("synthetic-implementors-list");
505 const inlined_types = new Set();
508 const SYNTHETIC_IDX = 1;
511 if (synthetic_implementors) {
512 // This `inlined_types` variable is used to avoid having the same implementation
513 // showing up twice. For example "String" in the "Sync" doc page.
515 // By the way, this is only used by and useful for traits implemented automatically
516 // (like "Send" and "Sync").
517 onEachLazy(synthetic_implementors.getElementsByClassName("impl"), el => {
518 const aliases = el.getAttribute("data-aliases");
522 aliases.split(",").forEach(alias => {
523 inlined_types.add(alias);
528 let currentNbImpls = implementors.getElementsByClassName("impl").length;
529 const traitName = document.querySelector(".main-heading h1 > .trait").textContent;
530 const baseIdName = "impl-" + traitName + "-";
531 const libs = Object.getOwnPropertyNames(imp);
532 // We don't want to include impls from this JS file, when the HTML already has them.
533 // The current crate should always be ignored. Other crates that should also be
534 // ignored are included in the attribute `data-ignore-extern-crates`.
535 const script = document
536 .querySelector("script[data-ignore-extern-crates]");
537 const ignoreExternCrates = script ? script.getAttribute("data-ignore-extern-crates") : "";
538 for (const lib of libs) {
539 if (lib === window.currentCrate || ignoreExternCrates.indexOf(lib) !== -1) {
542 const structs = imp[lib];
545 for (const struct of structs) {
546 const list = struct[SYNTHETIC_IDX] ? synthetic_implementors : implementors;
548 // The types list is only used for synthetic impls.
549 // If this changes, `main.js` and `write_shared.rs` both need changed.
550 if (struct[SYNTHETIC_IDX]) {
551 for (const struct_type of struct[TYPES_IDX]) {
552 if (inlined_types.has(struct_type)) {
553 continue struct_loop;
555 inlined_types.add(struct_type);
559 const code = document.createElement("h3");
560 code.innerHTML = struct[TEXT_IDX];
561 addClass(code, "code-header");
563 onEachLazy(code.getElementsByTagName("a"), elem => {
564 const href = elem.getAttribute("href");
566 if (href && !/^(?:[a-z+]+:)?\/\//.test(href)) {
567 elem.setAttribute("href", window.rootPath + href);
571 const currentId = baseIdName + currentNbImpls;
572 const anchor = document.createElement("a");
573 anchor.href = "#" + currentId;
574 addClass(anchor, "anchor");
576 const display = document.createElement("div");
577 display.id = currentId;
578 addClass(display, "impl");
579 display.appendChild(anchor);
580 display.appendChild(code);
581 list.appendChild(display);
586 if (window.pending_implementors) {
587 window.register_implementors(window.pending_implementors);
590 function addSidebarCrates() {
591 if (!window.ALL_CRATES) {
594 const sidebarElems = document.getElementsByClassName("sidebar-elems")[0];
598 // Draw a convenient sidebar of known crates if we have a listing
599 const h3 = document.createElement("h3");
600 h3.innerHTML = "Crates";
601 const ul = document.createElement("ul");
602 ul.className = "block crate";
604 for (const crate of window.ALL_CRATES) {
605 const link = document.createElement("a");
606 link.href = window.rootPath + crate + "/index.html";
607 if (window.rootPath !== "./" && crate === window.currentCrate) {
608 link.className = "current";
610 link.textContent = crate;
612 const li = document.createElement("li");
613 li.appendChild(link);
616 sidebarElems.appendChild(h3);
617 sidebarElems.appendChild(ul);
620 function expandAllDocs() {
621 const innerToggle = document.getElementById(toggleAllDocsId);
622 removeClass(innerToggle, "will-expand");
623 onEachLazy(document.getElementsByClassName("toggle"), e => {
624 if (!hasClass(e, "type-contents-toggle") && !hasClass(e, "more-examples-toggle")) {
628 innerToggle.title = "collapse all docs";
629 innerToggle.children[0].innerText = "\u2212"; // "\u2212" is "−" minus sign
632 function collapseAllDocs() {
633 const innerToggle = document.getElementById(toggleAllDocsId);
634 addClass(innerToggle, "will-expand");
635 onEachLazy(document.getElementsByClassName("toggle"), e => {
636 if (e.parentNode.id !== "implementations-list" ||
637 (!hasClass(e, "implementors-toggle") &&
638 !hasClass(e, "type-contents-toggle"))
643 innerToggle.title = "expand all docs";
644 innerToggle.children[0].innerText = "+";
647 function toggleAllDocs() {
648 const innerToggle = document.getElementById(toggleAllDocsId);
652 if (hasClass(innerToggle, "will-expand")) {
660 const toggles = document.getElementById(toggleAllDocsId);
662 toggles.onclick = toggleAllDocs;
665 const hideMethodDocs = getSettingValue("auto-hide-method-docs") === "true";
666 const hideImplementations = getSettingValue("auto-hide-trait-implementations") === "true";
667 const hideLargeItemContents = getSettingValue("auto-hide-large-items") !== "false";
669 function setImplementorsTogglesOpen(id, open) {
670 const list = document.getElementById(id);
672 onEachLazy(list.getElementsByClassName("implementors-toggle"), e => {
678 if (hideImplementations) {
679 setImplementorsTogglesOpen("trait-implementations-list", false);
680 setImplementorsTogglesOpen("blanket-implementations-list", false);
683 onEachLazy(document.getElementsByClassName("toggle"), e => {
684 if (!hideLargeItemContents && hasClass(e, "type-contents-toggle")) {
687 if (hideMethodDocs && hasClass(e, "method-toggle")) {
693 const pageId = getPageId();
694 if (pageId !== null) {
695 expandSection(pageId);
699 window.rustdoc_add_line_numbers_to_examples = () => {
700 onEachLazy(document.getElementsByClassName("rust-example-rendered"), x => {
701 const parent = x.parentNode;
702 const line_numbers = parent.querySelectorAll(".example-line-numbers");
703 if (line_numbers.length > 0) {
706 const count = x.textContent.split("\n").length;
708 for (let i = 0; i < count; ++i) {
711 const node = document.createElement("pre");
712 addClass(node, "example-line-numbers");
713 node.innerHTML = elems.join("\n");
714 parent.insertBefore(node, x);
718 window.rustdoc_remove_line_numbers_from_examples = () => {
719 onEachLazy(document.getElementsByClassName("rust-example-rendered"), x => {
720 const parent = x.parentNode;
721 const line_numbers = parent.querySelectorAll(".example-line-numbers");
722 for (const node of line_numbers) {
723 parent.removeChild(node);
728 if (getSettingValue("line-numbers") === "true") {
729 window.rustdoc_add_line_numbers_to_examples();
732 let oldSidebarScrollPosition = null;
734 // Scroll locking used both here and in source-script.js
736 window.rustdocMobileScrollLock = function() {
737 const mobile_topbar = document.querySelector(".mobile-topbar");
738 if (window.innerWidth <= window.RUSTDOC_MOBILE_BREAKPOINT) {
739 // This is to keep the scroll position on mobile.
740 oldSidebarScrollPosition = window.scrollY;
741 document.body.style.width = `${document.body.offsetWidth}px`;
742 document.body.style.position = "fixed";
743 document.body.style.top = `-${oldSidebarScrollPosition}px`;
745 mobile_topbar.style.top = `${oldSidebarScrollPosition}px`;
746 mobile_topbar.style.position = "relative";
749 oldSidebarScrollPosition = null;
753 window.rustdocMobileScrollUnlock = function() {
754 const mobile_topbar = document.querySelector(".mobile-topbar");
755 if (oldSidebarScrollPosition !== null) {
756 // This is to keep the scroll position on mobile.
757 document.body.style.width = "";
758 document.body.style.position = "";
759 document.body.style.top = "";
761 mobile_topbar.style.top = "";
762 mobile_topbar.style.position = "";
764 // The scroll position is lost when resetting the style, hence why we store it in
765 // `oldSidebarScrollPosition`.
766 window.scrollTo(0, oldSidebarScrollPosition);
767 oldSidebarScrollPosition = null;
771 function showSidebar() {
772 window.hideAllModals(false);
773 window.rustdocMobileScrollLock();
774 const sidebar = document.getElementsByClassName("sidebar")[0];
775 addClass(sidebar, "shown");
778 function hideSidebar() {
779 window.rustdocMobileScrollUnlock();
780 const sidebar = document.getElementsByClassName("sidebar")[0];
781 removeClass(sidebar, "shown");
784 window.addEventListener("resize", () => {
785 if (window.innerWidth > window.RUSTDOC_MOBILE_BREAKPOINT &&
786 oldSidebarScrollPosition !== null) {
787 // If the user opens the sidebar in "mobile" mode, and then grows the browser window,
788 // we need to switch away from mobile mode and make the main content area scrollable.
791 if (window.CURRENT_NOTABLE_ELEMENT) {
792 // As a workaround to the behavior of `contains: layout` used in doc togglers, the
793 // notable traits popup is positioned using javascript.
795 // This means when the window is resized, we need to redo the layout.
796 const base = window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE;
797 const force_visible = base.NOTABLE_FORCE_VISIBLE;
801 base.NOTABLE_FORCE_VISIBLE = true;
806 const mainElem = document.getElementById(MAIN_ID);
808 mainElem.addEventListener("click", hideSidebar);
811 onEachLazy(document.querySelectorAll("a[href^='#']"), el => {
812 // For clicks on internal links (<A> tags with a hash property), we expand the section we're
813 // jumping to *before* jumping there. We can't do this in onHashChange, because it changes
814 // the height of the document so we wind up scrolled to the wrong place.
815 el.addEventListener("click", () => {
816 expandSection(el.hash.slice(1));
821 onEachLazy(document.querySelectorAll(".toggle > summary:not(.hideme)"), el => {
822 el.addEventListener("click", e => {
823 if (e.target.tagName !== "SUMMARY" && e.target.tagName !== "A") {
829 function showNotable(e) {
830 if (!window.NOTABLE_TRAITS) {
831 const data = document.getElementById("notable-traits-data");
833 window.NOTABLE_TRAITS = JSON.parse(data.innerText);
835 throw new Error("showNotable() called on page without any notable traits!");
838 if (window.CURRENT_NOTABLE_ELEMENT && window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE === e) {
839 // Make this function idempotent.
842 window.hideAllModals(false);
843 const ty = e.getAttribute("data-ty");
844 const wrapper = document.createElement("div");
845 wrapper.innerHTML = "<div class=\"content\">" + window.NOTABLE_TRAITS[ty] + "</div>";
846 wrapper.className = "notable popover";
847 const focusCatcher = document.createElement("div");
848 focusCatcher.setAttribute("tabindex", "0");
849 focusCatcher.onfocus = hideNotable;
850 wrapper.appendChild(focusCatcher);
851 const pos = e.getBoundingClientRect();
852 // 5px overlap so that the mouse can easily travel from place to place
853 wrapper.style.top = (pos.top + window.scrollY + pos.height) + "px";
854 wrapper.style.left = 0;
855 wrapper.style.right = "auto";
856 wrapper.style.visibility = "hidden";
857 const body = document.getElementsByTagName("body")[0];
858 body.appendChild(wrapper);
859 const wrapperPos = wrapper.getBoundingClientRect();
860 // offset so that the arrow points at the center of the "(i)"
861 const finalPos = pos.left + window.scrollX - wrapperPos.width + 24;
863 wrapper.style.left = finalPos + "px";
865 wrapper.style.setProperty(
866 "--popover-arrow-offset",
867 (wrapperPos.right - pos.right + 4) + "px"
870 wrapper.style.visibility = "";
871 window.CURRENT_NOTABLE_ELEMENT = wrapper;
872 window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE = e;
873 wrapper.onpointerleave = function(ev) {
874 // If this is a synthetic touch event, ignore it. A click event will be along shortly.
875 if (ev.pointerType !== "mouse") {
878 if (!e.NOTABLE_FORCE_VISIBLE && !elemIsInParent(event.relatedTarget, e)) {
884 function notableBlurHandler(event) {
885 if (window.CURRENT_NOTABLE_ELEMENT &&
886 !elemIsInParent(document.activeElement, window.CURRENT_NOTABLE_ELEMENT) &&
887 !elemIsInParent(event.relatedTarget, window.CURRENT_NOTABLE_ELEMENT) &&
888 !elemIsInParent(document.activeElement, window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE) &&
889 !elemIsInParent(event.relatedTarget, window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE)
891 // Work around a difference in the focus behaviour between Firefox, Chrome, and Safari.
892 // When I click the button on an already-opened notable trait popover, Safari
893 // hides the popover and then immediately shows it again, while everyone else hides it
894 // and it stays hidden.
896 // To work around this, make sure the click finishes being dispatched before
897 // hiding the popover. Since `hideNotable()` is idempotent, this makes Safari behave
898 // consistently with the other two.
899 setTimeout(() => hideNotable(false), 0);
903 function hideNotable(focus) {
904 if (window.CURRENT_NOTABLE_ELEMENT) {
905 if (window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.NOTABLE_FORCE_VISIBLE) {
907 window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.focus();
909 window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.NOTABLE_FORCE_VISIBLE = false;
911 const body = document.getElementsByTagName("body")[0];
912 body.removeChild(window.CURRENT_NOTABLE_ELEMENT);
913 window.CURRENT_NOTABLE_ELEMENT = null;
917 onEachLazy(document.getElementsByClassName("notable-traits"), e => {
918 e.onclick = function() {
919 this.NOTABLE_FORCE_VISIBLE = this.NOTABLE_FORCE_VISIBLE ? false : true;
920 if (window.CURRENT_NOTABLE_ELEMENT && !this.NOTABLE_FORCE_VISIBLE) {
924 window.CURRENT_NOTABLE_ELEMENT.setAttribute("tabindex", "0");
925 window.CURRENT_NOTABLE_ELEMENT.focus();
926 window.CURRENT_NOTABLE_ELEMENT.onblur = notableBlurHandler;
930 e.onpointerenter = function(ev) {
931 // If this is a synthetic touch event, ignore it. A click event will be along shortly.
932 if (ev.pointerType !== "mouse") {
937 e.onpointerleave = function(ev) {
938 // If this is a synthetic touch event, ignore it. A click event will be along shortly.
939 if (ev.pointerType !== "mouse") {
942 if (!this.NOTABLE_FORCE_VISIBLE &&
943 !elemIsInParent(ev.relatedTarget, window.CURRENT_NOTABLE_ELEMENT)) {
949 const sidebar_menu_toggle = document.getElementsByClassName("sidebar-menu-toggle")[0];
950 if (sidebar_menu_toggle) {
951 sidebar_menu_toggle.addEventListener("click", () => {
952 const sidebar = document.getElementsByClassName("sidebar")[0];
953 if (!hasClass(sidebar, "shown")) {
961 function helpBlurHandler(event) {
962 blurHandler(event, getHelpButton(), window.hidePopoverMenus);
965 function buildHelpMenu() {
966 const book_info = document.createElement("span");
967 book_info.className = "top";
968 book_info.innerHTML = "You can find more information in \
969 <a href=\"https://doc.rust-lang.org/rustdoc/\">the rustdoc book</a>.";
972 ["?", "Show this help dialog"],
973 ["S", "Focus the search field"],
974 ["↑", "Move up in search results"],
975 ["↓", "Move down in search results"],
976 ["← / →", "Switch result tab (when results focused)"],
977 ["⏎", "Go to active search result"],
978 ["+", "Expand all sections"],
979 ["-", "Collapse all sections"],
982 .map((y, index) => ((index & 1) === 0 ? "<kbd>" + y + "</kbd>" : " " + y + " "))
983 .join("") + "</dt><dd>" + x[1] + "</dd>").join("");
984 const div_shortcuts = document.createElement("div");
985 addClass(div_shortcuts, "shortcuts");
986 div_shortcuts.innerHTML = "<h2>Keyboard Shortcuts</h2><dl>" + shortcuts + "</dl></div>";
989 "Prefix searches with a type followed by a colon (e.g., <code>fn:</code>) to \
990 restrict the search to a given item kind.",
991 "Accepted kinds are: <code>fn</code>, <code>mod</code>, <code>struct</code>, \
992 <code>enum</code>, <code>trait</code>, <code>type</code>, <code>macro</code>, \
993 and <code>const</code>.",
994 "Search functions by type signature (e.g., <code>vec -> usize</code> or \
995 <code>-> vec</code>)",
996 "Search multiple things at once by splitting your query with comma (e.g., \
997 <code>str,u8</code> or <code>String,struct:Vec,test</code>)",
998 "You can look for items with an exact name by putting double quotes around \
999 your request: <code>\"string\"</code>",
1000 "Look for items inside another one by searching for a path: <code>vec::Vec</code>",
1001 ].map(x => "<p>" + x + "</p>").join("");
1002 const div_infos = document.createElement("div");
1003 addClass(div_infos, "infos");
1004 div_infos.innerHTML = "<h2>Search Tricks</h2>" + infos;
1006 const rustdoc_version = document.createElement("span");
1007 rustdoc_version.className = "bottom";
1008 const rustdoc_version_code = document.createElement("code");
1009 rustdoc_version_code.innerText = "rustdoc " + getVar("rustdoc-version");
1010 rustdoc_version.appendChild(rustdoc_version_code);
1012 const container = document.createElement("div");
1014 container.className = "popover";
1016 container.id = "help";
1017 container.style.display = "none";
1019 const side_by_side = document.createElement("div");
1020 side_by_side.className = "side-by-side";
1021 side_by_side.appendChild(div_shortcuts);
1022 side_by_side.appendChild(div_infos);
1024 container.appendChild(book_info);
1025 container.appendChild(side_by_side);
1026 container.appendChild(rustdoc_version);
1029 const help_section = document.createElement("section");
1030 help_section.appendChild(container);
1031 document.getElementById("main-content").appendChild(help_section);
1032 container.style.display = "block";
1034 const help_button = getHelpButton();
1035 help_button.appendChild(container);
1037 container.onblur = helpBlurHandler;
1038 help_button.onblur = helpBlurHandler;
1039 help_button.children[0].onblur = helpBlurHandler;
1046 * Hide popover menus, notable trait tooltips, and the sidebar (if applicable).
1048 * Pass "true" to reset focus for notable traits.
1050 window.hideAllModals = function(switchFocus) {
1052 window.hidePopoverMenus();
1053 hideNotable(switchFocus);
1057 * Hide all the popover menus.
1059 window.hidePopoverMenus = function() {
1060 onEachLazy(document.querySelectorAll(".search-form .popover"), elem => {
1061 elem.style.display = "none";
1066 * Returns the help menu element (not the button).
1068 * @param {boolean} buildNeeded - If this argument is `false`, the help menu element won't be
1069 * built if it doesn't exist.
1071 * @return {HTMLElement}
1073 function getHelpMenu(buildNeeded) {
1074 let menu = getHelpButton().querySelector(".popover");
1075 if (!menu && buildNeeded) {
1076 menu = buildHelpMenu();
1082 * Show the help popup menu.
1084 function showHelp() {
1085 // Prevent `blur` events from being dispatched as a result of closing
1087 getHelpButton().querySelector("a").focus();
1088 const menu = getHelpMenu(true);
1089 if (menu.style.display === "none") {
1090 window.hideAllModals();
1091 menu.style.display = "";
1097 document.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click", event => {
1098 // Already on the help page, make help button a no-op.
1099 const target = event.target;
1100 if (target.tagName !== "A" ||
1101 target.parentElement.id !== HELP_BUTTON_ID ||
1107 event.preventDefault();
1110 document.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click", event => {
1111 // By default, have help button open docs in a popover.
1112 // If user clicks with a moderator, though, use default browser behavior,
1113 // probably opening in a new window or tab.
1114 const target = event.target;
1115 if (target.tagName !== "A" ||
1116 target.parentElement.id !== HELP_BUTTON_ID ||
1122 event.preventDefault();
1123 const menu = getHelpMenu(true);
1124 const shouldShowHelp = menu.style.display === "none";
1125 if (shouldShowHelp) {
1128 window.hidePopoverMenus();
1137 window.addEventListener("hashchange", onHashChange);
1138 searchState.setup();
1142 let reset_button_timeout = null;
1144 const but = document.getElementById("copy-path");
1148 but.onclick = () => {
1149 const parent = but.parentElement;
1152 onEach(parent.childNodes, child => {
1153 if (child.tagName === "A") {
1154 path.push(child.textContent);
1158 const el = document.createElement("textarea");
1159 el.value = path.join("::");
1160 el.setAttribute("readonly", "");
1161 // To not make it appear on the screen.
1162 el.style.position = "absolute";
1163 el.style.left = "-9999px";
1165 document.body.appendChild(el);
1167 document.execCommand("copy");
1168 document.body.removeChild(el);
1170 // There is always one children, but multiple childNodes.
1171 but.children[0].style.display = "none";
1174 if (but.childNodes.length < 2) {
1175 tmp = document.createTextNode("✓");
1176 but.appendChild(tmp);
1178 onEachLazy(but.childNodes, e => {
1179 if (e.nodeType === Node.TEXT_NODE) {
1184 tmp.textContent = "✓";
1187 if (reset_button_timeout !== null) {
1188 window.clearTimeout(reset_button_timeout);
1191 function reset_button() {
1192 tmp.textContent = "";
1193 reset_button_timeout = null;
1194 but.children[0].style.display = "";
1197 reset_button_timeout = window.setTimeout(reset_button, 1000);