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 // eslint-disable-next-line no-unused-vars
184 function loadCss(cssUrl
) {
185 const link
= document
.createElement("link");
187 link
.rel
= "stylesheet";
188 document
.getElementsByTagName("head")[0].appendChild(link
);
192 const isHelpPage
= window
.location
.pathname
.endsWith("/help.html");
194 function loadScript(url
) {
195 const script
= document
.createElement("script");
197 document
.head
.append(script
);
200 getSettingsButton().onclick
= event
=> {
201 if (event
.ctrlKey
|| event
.altKey
|| event
.metaKey
) {
204 window
.hideAllModals(false);
205 addClass(getSettingsButton(), "rotate");
206 event
.preventDefault();
207 // Sending request for the CSS and the JS files at the same time so it will
208 // hopefully be loaded when the JS will generate the settings content.
209 loadCss(getVar("static-root-path") + getVar("settings-css"));
210 loadScript(getVar("static-root-path") + getVar("settings-js"));
213 window
.searchState
= {
214 loadingText
: "Loading search results...",
215 input
: document
.getElementsByClassName("search-input")[0],
216 outputElement
: () => {
217 let el
= document
.getElementById("search");
219 el
= document
.createElement("section");
221 getNotDisplayedElem().appendChild(el
);
225 title
: document
.title
,
226 titleBeforeSearch
: document
.title
,
228 // On the search screen, so you remain on the last tab you opened.
231 // 1 for "In Parameters"
232 // 2 for "In Return Types"
234 // tab and back preserves the element that was focused.
235 focusedByTab
: [null, null, null],
236 clearInputTimeout
: () => {
237 if (searchState
.timeout
!== null) {
238 clearTimeout(searchState
.timeout
);
239 searchState
.timeout
= null;
242 isDisplayed
: () => searchState
.outputElement().parentElement
.id
=== ALTERNATIVE_DISPLAY_ID
,
243 // Sets the focus on the search bar at the top of the page
245 searchState
.input
.focus();
247 // Removes the focus from the search bar.
249 searchState
.input
.blur();
251 showResults
: search
=> {
252 if (search
=== null || typeof search
=== "undefined") {
253 search
= searchState
.outputElement();
255 switchDisplayedElement(search
);
256 searchState
.mouseMovedAfterSearch
= false;
257 document
.title
= searchState
.title
;
260 switchDisplayedElement(null);
261 document
.title
= searchState
.titleBeforeSearch
;
262 // We also remove the query parameter from the URL.
263 if (browserSupportsHistoryApi()) {
264 history
.replaceState(null, window
.currentCrate
+ " - Rust",
265 getNakedUrl() + window
.location
.hash
);
268 getQueryStringParams
: () => {
270 window
.location
.search
.substring(1).split("&").
272 const pair
= s
.split("=");
273 params
[decodeURIComponent(pair
[0])] =
274 typeof pair
[1] === "undefined" ? null : decodeURIComponent(pair
[1]);
279 const search_input
= searchState
.input
;
280 if (!searchState
.input
) {
283 let searchLoaded
= false;
284 function loadSearch() {
287 loadScript(getVar("static-root-path") + getVar("search-js"));
288 loadScript(resourcePath("search-index", ".js"));
292 search_input
.addEventListener("focus", () => {
293 search_input
.origPlaceholder
= search_input
.placeholder
;
294 search_input
.placeholder
= "Type your search here.";
298 if (search_input
.value
!== "") {
302 const params
= searchState
.getQueryStringParams();
303 if (params
.search
!== undefined) {
304 searchState
.setLoadingSearch();
308 setLoadingSearch
: () => {
309 const search
= searchState
.outputElement();
310 search
.innerHTML
= "<h3 class=\"search-loading\">" + searchState
.loadingText
+ "</h3>";
311 searchState
.showResults(search
);
315 function getPageId() {
316 if (window
.location
.hash
) {
317 const tmp
= window
.location
.hash
.replace(/^#/, "");
318 if (tmp
.length
> 0) {
325 const toggleAllDocsId
= "toggle-all-docs";
328 function handleHashes(ev
) {
329 if (ev
!== null && searchState
.isDisplayed() && ev
.newURL
) {
330 // This block occurs when clicking on an element in the navbar while
332 switchDisplayedElement(null);
333 const hash
= ev
.newURL
.slice(ev
.newURL
.indexOf("#") + 1);
334 if (browserSupportsHistoryApi()) {
335 // `window.location.search`` contains all the query parameters, not just `search`.
336 history
.replaceState(null, "",
337 getNakedUrl() + window
.location
.search
+ "#" + hash
);
339 const elem
= document
.getElementById(hash
);
341 elem
.scrollIntoView();
344 // This part is used in case an element is not visible.
345 if (savedHash
!== window
.location
.hash
) {
346 savedHash
= window
.location
.hash
;
347 if (savedHash
.length
=== 0) {
350 expandSection(savedHash
.slice(1)); // we remove the '#'
354 function onHashChange(ev
) {
355 // If we're in mobile mode, we should hide the sidebar in any case.
360 function openParentDetails(elem
) {
362 if (elem
.tagName
=== "DETAILS") {
365 elem
= elem
.parentNode
;
369 function expandSection(id
) {
370 openParentDetails(document
.getElementById(id
));
373 function handleEscape(ev
) {
374 searchState
.clearInputTimeout();
375 switchDisplayedElement(null);
376 if (browserSupportsHistoryApi()) {
377 history
.replaceState(null, window
.currentCrate
+ " - Rust",
378 getNakedUrl() + window
.location
.hash
);
381 searchState
.defocus();
382 window
.hideAllModals(true); // true = reset focus for notable traits
385 function handleShortcut(ev
) {
386 // Don't interfere with browser shortcuts
387 const disableShortcuts
= getSettingValue("disable-shortcuts") === "true";
388 if (ev
.ctrlKey
|| ev
.altKey
|| ev
.metaKey
|| disableShortcuts
) {
392 if (document
.activeElement
.tagName
=== "INPUT" &&
393 document
.activeElement
.type
!== "checkbox" &&
394 document
.activeElement
.type
!== "radio") {
395 switch (getVirtualKey(ev
)) {
401 switch (getVirtualKey(ev
)) {
431 document
.addEventListener("keypress", handleShortcut
);
432 document
.addEventListener("keydown", handleShortcut
);
434 function addSidebarItems() {
435 if (!window
.SIDEBAR_ITEMS
) {
438 const sidebar
= document
.getElementsByClassName("sidebar-elems")[0];
441 * Append to the sidebar a "block" of links - a heading along with a list (`<ul>`) of items.
443 * @param {string} shortty - A short type name, like "primitive", "mod", or "macro"
444 * @param {string} id - The HTML id of the corresponding section on the module page.
445 * @param {string} longty - A long, capitalized, plural name, like "Primitive Types",
446 * "Modules", or "Macros".
448 function block(shortty
, id
, longty
) {
449 const filtered
= window
.SIDEBAR_ITEMS
[shortty
];
454 const h3
= document
.createElement("h3");
455 h3
.innerHTML
= `<a href="index.html#${id}">${longty}</a>`;
456 const ul
= document
.createElement("ul");
457 ul
.className
= "block " + shortty
;
459 for (const item
of filtered
) {
460 const name
= item
[0];
461 const desc
= item
[1]; // can be null
464 if (shortty
=== "mod") {
465 path
= name
+ "/index.html";
467 path
= shortty
+ "." + name
+ ".html";
469 const current_page
= document
.location
.href
.split("/").pop();
470 const link
= document
.createElement("a");
473 if (path
=== current_page
) {
474 link
.className
= "current";
476 link
.textContent
= name
;
477 const li
= document
.createElement("li");
478 li
.appendChild(link
);
481 sidebar
.appendChild(h3
);
482 sidebar
.appendChild(ul
);
486 block("primitive", "primitives", "Primitive Types");
487 block("mod", "modules", "Modules");
488 block("macro", "macros", "Macros");
489 block("struct", "structs", "Structs");
490 block("enum", "enums", "Enums");
491 block("union", "unions", "Unions");
492 block("constant", "constants", "Constants");
493 block("static", "static", "Statics");
494 block("trait", "traits", "Traits");
495 block("fn", "functions", "Functions");
496 block("type", "types", "Type Definitions");
497 block("foreigntype", "foreign-types", "Foreign Types");
498 block("keyword", "keywords", "Keywords");
499 block("traitalias", "trait-aliases", "Trait Aliases");
503 window
.register_implementors
= imp
=> {
504 const implementors
= document
.getElementById("implementors-list");
505 const synthetic_implementors
= document
.getElementById("synthetic-implementors-list");
506 const inlined_types
= new Set();
509 const SYNTHETIC_IDX
= 1;
512 if (synthetic_implementors
) {
513 // This `inlined_types` variable is used to avoid having the same implementation
514 // showing up twice. For example "String" in the "Sync" doc page.
516 // By the way, this is only used by and useful for traits implemented automatically
517 // (like "Send" and "Sync").
518 onEachLazy(synthetic_implementors
.getElementsByClassName("impl"), el
=> {
519 const aliases
= el
.getAttribute("data-aliases");
523 aliases
.split(",").forEach(alias
=> {
524 inlined_types
.add(alias
);
529 let currentNbImpls
= implementors
.getElementsByClassName("impl").length
;
530 const traitName
= document
.querySelector(".main-heading h1 > .trait").textContent
;
531 const baseIdName
= "impl-" + traitName
+ "-";
532 const libs
= Object
.getOwnPropertyNames(imp
);
533 // We don't want to include impls from this JS file, when the HTML already has them.
534 // The current crate should always be ignored. Other crates that should also be
535 // ignored are included in the attribute `data-ignore-extern-crates`.
536 const script
= document
537 .querySelector("script[data-ignore-extern-crates]");
538 const ignoreExternCrates
= script
? script
.getAttribute("data-ignore-extern-crates") : "";
539 for (const lib
of libs
) {
540 if (lib
=== window
.currentCrate
|| ignoreExternCrates
.indexOf(lib
) !== -1) {
543 const structs
= imp
[lib
];
546 for (const struct
of structs
) {
547 const list
= struct
[SYNTHETIC_IDX
] ? synthetic_implementors
: implementors
;
549 // The types list is only used for synthetic impls.
550 // If this changes, `main.js` and `write_shared.rs` both need changed.
551 if (struct
[SYNTHETIC_IDX
]) {
552 for (const struct_type
of struct
[TYPES_IDX
]) {
553 if (inlined_types
.has(struct_type
)) {
554 continue struct_loop
;
556 inlined_types
.add(struct_type
);
560 const code
= document
.createElement("h3");
561 code
.innerHTML
= struct
[TEXT_IDX
];
562 addClass(code
, "code-header");
564 onEachLazy(code
.getElementsByTagName("a"), elem
=> {
565 const href
= elem
.getAttribute("href");
567 if (href
&& !/^(?:[a-z+]+:)?\/\//.test(href
)) {
568 elem
.setAttribute("href", window
.rootPath
+ href
);
572 const currentId
= baseIdName
+ currentNbImpls
;
573 const anchor
= document
.createElement("a");
574 anchor
.href
= "#" + currentId
;
575 addClass(anchor
, "anchor");
577 const display
= document
.createElement("div");
578 display
.id
= currentId
;
579 addClass(display
, "impl");
580 display
.appendChild(anchor
);
581 display
.appendChild(code
);
582 list
.appendChild(display
);
587 if (window
.pending_implementors
) {
588 window
.register_implementors(window
.pending_implementors
);
591 function addSidebarCrates() {
592 if (!window
.ALL_CRATES
) {
595 const sidebarElems
= document
.getElementsByClassName("sidebar-elems")[0];
599 // Draw a convenient sidebar of known crates if we have a listing
600 const h3
= document
.createElement("h3");
601 h3
.innerHTML
= "Crates";
602 const ul
= document
.createElement("ul");
603 ul
.className
= "block crate";
605 for (const crate
of window
.ALL_CRATES
) {
606 const link
= document
.createElement("a");
607 link
.href
= window
.rootPath
+ crate
+ "/index.html";
608 if (window
.rootPath
!== "./" && crate
=== window
.currentCrate
) {
609 link
.className
= "current";
611 link
.textContent
= crate
;
613 const li
= document
.createElement("li");
614 li
.appendChild(link
);
617 sidebarElems
.appendChild(h3
);
618 sidebarElems
.appendChild(ul
);
621 function expandAllDocs() {
622 const innerToggle
= document
.getElementById(toggleAllDocsId
);
623 removeClass(innerToggle
, "will-expand");
624 onEachLazy(document
.getElementsByClassName("toggle"), e
=> {
625 if (!hasClass(e
, "type-contents-toggle") && !hasClass(e
, "more-examples-toggle")) {
629 innerToggle
.title
= "collapse all docs";
630 innerToggle
.children
[0].innerText
= "\u2212"; // "\u2212" is "−" minus sign
633 function collapseAllDocs() {
634 const innerToggle
= document
.getElementById(toggleAllDocsId
);
635 addClass(innerToggle
, "will-expand");
636 onEachLazy(document
.getElementsByClassName("toggle"), e
=> {
637 if (e
.parentNode
.id
!== "implementations-list" ||
638 (!hasClass(e
, "implementors-toggle") &&
639 !hasClass(e
, "type-contents-toggle"))
644 innerToggle
.title
= "expand all docs";
645 innerToggle
.children
[0].innerText
= "+";
648 function toggleAllDocs() {
649 const innerToggle
= document
.getElementById(toggleAllDocsId
);
653 if (hasClass(innerToggle
, "will-expand")) {
661 const toggles
= document
.getElementById(toggleAllDocsId
);
663 toggles
.onclick
= toggleAllDocs
;
666 const hideMethodDocs
= getSettingValue("auto-hide-method-docs") === "true";
667 const hideImplementations
= getSettingValue("auto-hide-trait-implementations") === "true";
668 const hideLargeItemContents
= getSettingValue("auto-hide-large-items") !== "false";
670 function setImplementorsTogglesOpen(id
, open
) {
671 const list
= document
.getElementById(id
);
673 onEachLazy(list
.getElementsByClassName("implementors-toggle"), e
=> {
679 if (hideImplementations
) {
680 setImplementorsTogglesOpen("trait-implementations-list", false);
681 setImplementorsTogglesOpen("blanket-implementations-list", false);
684 onEachLazy(document
.getElementsByClassName("toggle"), e
=> {
685 if (!hideLargeItemContents
&& hasClass(e
, "type-contents-toggle")) {
688 if (hideMethodDocs
&& hasClass(e
, "method-toggle")) {
694 const pageId
= getPageId();
695 if (pageId
!== null) {
696 expandSection(pageId
);
700 window
.rustdoc_add_line_numbers_to_examples
= () => {
701 onEachLazy(document
.getElementsByClassName("rust-example-rendered"), x
=> {
702 const parent
= x
.parentNode
;
703 const line_numbers
= parent
.querySelectorAll(".example-line-numbers");
704 if (line_numbers
.length
> 0) {
707 const count
= x
.textContent
.split("\n").length
;
709 for (let i
= 0; i
< count
; ++i
) {
712 const node
= document
.createElement("pre");
713 addClass(node
, "example-line-numbers");
714 node
.innerHTML
= elems
.join("\n");
715 parent
.insertBefore(node
, x
);
719 window
.rustdoc_remove_line_numbers_from_examples
= () => {
720 onEachLazy(document
.getElementsByClassName("rust-example-rendered"), x
=> {
721 const parent
= x
.parentNode
;
722 const line_numbers
= parent
.querySelectorAll(".example-line-numbers");
723 for (const node
of line_numbers
) {
724 parent
.removeChild(node
);
729 if (getSettingValue("line-numbers") === "true") {
730 window
.rustdoc_add_line_numbers_to_examples();
733 let oldSidebarScrollPosition
= null;
735 // Scroll locking used both here and in source-script.js
737 window
.rustdocMobileScrollLock = function() {
738 const mobile_topbar
= document
.querySelector(".mobile-topbar");
739 if (window
.innerWidth
<= window
.RUSTDOC_MOBILE_BREAKPOINT
) {
740 // This is to keep the scroll position on mobile.
741 oldSidebarScrollPosition
= window
.scrollY
;
742 document
.body
.style
.width
= `${document.body.offsetWidth}px`;
743 document
.body
.style
.position
= "fixed";
744 document
.body
.style
.top
= `-${oldSidebarScrollPosition}px`;
746 mobile_topbar
.style
.top
= `${oldSidebarScrollPosition}px`;
747 mobile_topbar
.style
.position
= "relative";
750 oldSidebarScrollPosition
= null;
754 window
.rustdocMobileScrollUnlock = function() {
755 const mobile_topbar
= document
.querySelector(".mobile-topbar");
756 if (oldSidebarScrollPosition
!== null) {
757 // This is to keep the scroll position on mobile.
758 document
.body
.style
.width
= "";
759 document
.body
.style
.position
= "";
760 document
.body
.style
.top
= "";
762 mobile_topbar
.style
.top
= "";
763 mobile_topbar
.style
.position
= "";
765 // The scroll position is lost when resetting the style, hence why we store it in
766 // `oldSidebarScrollPosition`.
767 window
.scrollTo(0, oldSidebarScrollPosition
);
768 oldSidebarScrollPosition
= null;
772 function showSidebar() {
773 window
.hideAllModals(false);
774 window
.rustdocMobileScrollLock();
775 const sidebar
= document
.getElementsByClassName("sidebar")[0];
776 addClass(sidebar
, "shown");
779 function hideSidebar() {
780 window
.rustdocMobileScrollUnlock();
781 const sidebar
= document
.getElementsByClassName("sidebar")[0];
782 removeClass(sidebar
, "shown");
785 window
.addEventListener("resize", () => {
786 if (window
.innerWidth
> window
.RUSTDOC_MOBILE_BREAKPOINT
&&
787 oldSidebarScrollPosition
!== null) {
788 // If the user opens the sidebar in "mobile" mode, and then grows the browser window,
789 // we need to switch away from mobile mode and make the main content area scrollable.
792 if (window
.CURRENT_NOTABLE_ELEMENT
) {
793 // As a workaround to the behavior of `contains: layout` used in doc togglers, the
794 // notable traits popup is positioned using javascript.
796 // This means when the window is resized, we need to redo the layout.
797 const base
= window
.CURRENT_NOTABLE_ELEMENT
.NOTABLE_BASE
;
798 const force_visible
= base
.NOTABLE_FORCE_VISIBLE
;
802 base
.NOTABLE_FORCE_VISIBLE
= true;
807 const mainElem
= document
.getElementById(MAIN_ID
);
809 mainElem
.addEventListener("click", hideSidebar
);
812 onEachLazy(document
.querySelectorAll("a[href^='#']"), el
=> {
813 // For clicks on internal links (<A> tags with a hash property), we expand the section we're
814 // jumping to *before* jumping there. We can't do this in onHashChange, because it changes
815 // the height of the document so we wind up scrolled to the wrong place.
816 el
.addEventListener("click", () => {
817 expandSection(el
.hash
.slice(1));
822 onEachLazy(document
.querySelectorAll(".toggle > summary:not(.hideme)"), el
=> {
823 el
.addEventListener("click", e
=> {
824 if (e
.target
.tagName
!== "SUMMARY" && e
.target
.tagName
!== "A") {
830 function showNotable(e
) {
831 if (!window
.NOTABLE_TRAITS
) {
832 const data
= document
.getElementById("notable-traits-data");
834 window
.NOTABLE_TRAITS
= JSON
.parse(data
.innerText
);
836 throw new Error("showNotable() called on page without any notable traits!");
839 if (window
.CURRENT_NOTABLE_ELEMENT
&& window
.CURRENT_NOTABLE_ELEMENT
.NOTABLE_BASE
=== e
) {
840 // Make this function idempotent.
843 window
.hideAllModals(false);
844 const ty
= e
.getAttribute("data-ty");
845 const wrapper
= document
.createElement("div");
846 wrapper
.innerHTML
= "<div class=\"content\">" + window
.NOTABLE_TRAITS
[ty
] + "</div>";
847 wrapper
.className
= "notable popover";
848 const focusCatcher
= document
.createElement("div");
849 focusCatcher
.setAttribute("tabindex", "0");
850 focusCatcher
.onfocus
= hideNotable
;
851 wrapper
.appendChild(focusCatcher
);
852 const pos
= e
.getBoundingClientRect();
853 // 5px overlap so that the mouse can easily travel from place to place
854 wrapper
.style
.top
= (pos
.top
+ window
.scrollY
+ pos
.height
) + "px";
855 wrapper
.style
.left
= 0;
856 wrapper
.style
.right
= "auto";
857 wrapper
.style
.visibility
= "hidden";
858 const body
= document
.getElementsByTagName("body")[0];
859 body
.appendChild(wrapper
);
860 const wrapperPos
= wrapper
.getBoundingClientRect();
861 // offset so that the arrow points at the center of the "(i)"
862 const finalPos
= pos
.left
+ window
.scrollX
- wrapperPos
.width
+ 24;
864 wrapper
.style
.left
= finalPos
+ "px";
866 wrapper
.style
.setProperty(
867 "--popover-arrow-offset",
868 (wrapperPos
.right
- pos
.right
+ 4) + "px"
871 wrapper
.style
.visibility
= "";
872 window
.CURRENT_NOTABLE_ELEMENT
= wrapper
;
873 window
.CURRENT_NOTABLE_ELEMENT
.NOTABLE_BASE
= e
;
874 wrapper
.onpointerleave = function(ev
) {
875 // If this is a synthetic touch event, ignore it. A click event will be along shortly.
876 if (ev
.pointerType
!== "mouse") {
879 if (!e
.NOTABLE_FORCE_VISIBLE
&& !elemIsInParent(event
.relatedTarget
, e
)) {
885 function notableBlurHandler(event
) {
886 if (window
.CURRENT_NOTABLE_ELEMENT
&&
887 !elemIsInParent(document
.activeElement
, window
.CURRENT_NOTABLE_ELEMENT
) &&
888 !elemIsInParent(event
.relatedTarget
, window
.CURRENT_NOTABLE_ELEMENT
) &&
889 !elemIsInParent(document
.activeElement
, window
.CURRENT_NOTABLE_ELEMENT
.NOTABLE_BASE
) &&
890 !elemIsInParent(event
.relatedTarget
, window
.CURRENT_NOTABLE_ELEMENT
.NOTABLE_BASE
)
892 // Work around a difference in the focus behaviour between Firefox, Chrome, and Safari.
893 // When I click the button on an already-opened notable trait popover, Safari
894 // hides the popover and then immediately shows it again, while everyone else hides it
895 // and it stays hidden.
897 // To work around this, make sure the click finishes being dispatched before
898 // hiding the popover. Since `hideNotable()` is idempotent, this makes Safari behave
899 // consistently with the other two.
900 setTimeout(() => hideNotable(false), 0);
904 function hideNotable(focus
) {
905 if (window
.CURRENT_NOTABLE_ELEMENT
) {
906 if (window
.CURRENT_NOTABLE_ELEMENT
.NOTABLE_BASE
.NOTABLE_FORCE_VISIBLE
) {
908 window
.CURRENT_NOTABLE_ELEMENT
.NOTABLE_BASE
.focus();
910 window
.CURRENT_NOTABLE_ELEMENT
.NOTABLE_BASE
.NOTABLE_FORCE_VISIBLE
= false;
912 const body
= document
.getElementsByTagName("body")[0];
913 body
.removeChild(window
.CURRENT_NOTABLE_ELEMENT
);
914 window
.CURRENT_NOTABLE_ELEMENT
= null;
918 onEachLazy(document
.getElementsByClassName("notable-traits"), e
=> {
919 e
.onclick = function() {
920 this.NOTABLE_FORCE_VISIBLE
= this.NOTABLE_FORCE_VISIBLE
? false : true;
921 if (window
.CURRENT_NOTABLE_ELEMENT
&& !this.NOTABLE_FORCE_VISIBLE
) {
925 window
.CURRENT_NOTABLE_ELEMENT
.setAttribute("tabindex", "0");
926 window
.CURRENT_NOTABLE_ELEMENT
.focus();
927 window
.CURRENT_NOTABLE_ELEMENT
.onblur
= notableBlurHandler
;
931 e
.onpointerenter = function(ev
) {
932 // If this is a synthetic touch event, ignore it. A click event will be along shortly.
933 if (ev
.pointerType
!== "mouse") {
938 e
.onpointerleave = function(ev
) {
939 // If this is a synthetic touch event, ignore it. A click event will be along shortly.
940 if (ev
.pointerType
!== "mouse") {
943 if (!this.NOTABLE_FORCE_VISIBLE
&&
944 !elemIsInParent(ev
.relatedTarget
, window
.CURRENT_NOTABLE_ELEMENT
)) {
950 const sidebar_menu_toggle
= document
.getElementsByClassName("sidebar-menu-toggle")[0];
951 if (sidebar_menu_toggle
) {
952 sidebar_menu_toggle
.addEventListener("click", () => {
953 const sidebar
= document
.getElementsByClassName("sidebar")[0];
954 if (!hasClass(sidebar
, "shown")) {
962 function helpBlurHandler(event
) {
963 blurHandler(event
, getHelpButton(), window
.hidePopoverMenus
);
966 function buildHelpMenu() {
967 const book_info
= document
.createElement("span");
968 book_info
.className
= "top";
969 book_info
.innerHTML
= "You can find more information in \
970 <a href=\"https://doc.rust-lang.org/rustdoc/\">the rustdoc book</a>.";
973 ["?", "Show this help dialog"],
974 ["S", "Focus the search field"],
975 ["↑", "Move up in search results"],
976 ["↓", "Move down in search results"],
977 ["← / →", "Switch result tab (when results focused)"],
978 ["⏎", "Go to active search result"],
979 ["+", "Expand all sections"],
980 ["-", "Collapse all sections"],
983 .map((y
, index
) => ((index
& 1) === 0 ? "<kbd>" + y
+ "</kbd>" : " " + y
+ " "))
984 .join("") + "</dt><dd>" + x
[1] + "</dd>").join("");
985 const div_shortcuts
= document
.createElement("div");
986 addClass(div_shortcuts
, "shortcuts");
987 div_shortcuts
.innerHTML
= "<h2>Keyboard Shortcuts</h2><dl>" + shortcuts
+ "</dl></div>";
990 "Prefix searches with a type followed by a colon (e.g., <code>fn:</code>) to \
991 restrict the search to a given item kind.",
992 "Accepted kinds are: <code>fn</code>, <code>mod</code>, <code>struct</code>, \
993 <code>enum</code>, <code>trait</code>, <code>type</code>, <code>macro</code>, \
994 and <code>const</code>.",
995 "Search functions by type signature (e.g., <code>vec -> usize</code> or \
996 <code>-> vec</code>)",
997 "Search multiple things at once by splitting your query with comma (e.g., \
998 <code>str,u8</code> or <code>String,struct:Vec,test</code>)",
999 "You can look for items with an exact name by putting double quotes around \
1000 your request: <code>\"string\"</code>",
1001 "Look for items inside another one by searching for a path: <code>vec::Vec</code>",
1002 ].map(x
=> "<p>" + x
+ "</p>").join("");
1003 const div_infos
= document
.createElement("div");
1004 addClass(div_infos
, "infos");
1005 div_infos
.innerHTML
= "<h2>Search Tricks</h2>" + infos
;
1007 const rustdoc_version
= document
.createElement("span");
1008 rustdoc_version
.className
= "bottom";
1009 const rustdoc_version_code
= document
.createElement("code");
1010 rustdoc_version_code
.innerText
= "rustdoc " + getVar("rustdoc-version");
1011 rustdoc_version
.appendChild(rustdoc_version_code
);
1013 const container
= document
.createElement("div");
1015 container
.className
= "popover";
1017 container
.id
= "help";
1018 container
.style
.display
= "none";
1020 const side_by_side
= document
.createElement("div");
1021 side_by_side
.className
= "side-by-side";
1022 side_by_side
.appendChild(div_shortcuts
);
1023 side_by_side
.appendChild(div_infos
);
1025 container
.appendChild(book_info
);
1026 container
.appendChild(side_by_side
);
1027 container
.appendChild(rustdoc_version
);
1030 const help_section
= document
.createElement("section");
1031 help_section
.appendChild(container
);
1032 document
.getElementById("main-content").appendChild(help_section
);
1033 container
.style
.display
= "block";
1035 const help_button
= getHelpButton();
1036 help_button
.appendChild(container
);
1038 container
.onblur
= helpBlurHandler
;
1039 help_button
.onblur
= helpBlurHandler
;
1040 help_button
.children
[0].onblur
= helpBlurHandler
;
1047 * Hide popover menus, notable trait tooltips, and the sidebar (if applicable).
1049 * Pass "true" to reset focus for notable traits.
1051 window
.hideAllModals = function(switchFocus
) {
1053 window
.hidePopoverMenus();
1054 hideNotable(switchFocus
);
1058 * Hide all the popover menus.
1060 window
.hidePopoverMenus = function() {
1061 onEachLazy(document
.querySelectorAll(".search-form .popover"), elem
=> {
1062 elem
.style
.display
= "none";
1067 * Returns the help menu element (not the button).
1069 * @param {boolean} buildNeeded - If this argument is `false`, the help menu element won't be
1070 * built if it doesn't exist.
1072 * @return {HTMLElement}
1074 function getHelpMenu(buildNeeded
) {
1075 let menu
= getHelpButton().querySelector(".popover");
1076 if (!menu
&& buildNeeded
) {
1077 menu
= buildHelpMenu();
1083 * Show the help popup menu.
1085 function showHelp() {
1086 // Prevent `blur` events from being dispatched as a result of closing
1088 getHelpButton().querySelector("a").focus();
1089 const menu
= getHelpMenu(true);
1090 if (menu
.style
.display
=== "none") {
1091 window
.hideAllModals();
1092 menu
.style
.display
= "";
1098 document
.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click", event
=> {
1099 // Already on the help page, make help button a no-op.
1100 const target
= event
.target
;
1101 if (target
.tagName
!== "A" ||
1102 target
.parentElement
.id
!== HELP_BUTTON_ID
||
1108 event
.preventDefault();
1111 document
.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click", event
=> {
1112 // By default, have help button open docs in a popover.
1113 // If user clicks with a moderator, though, use default browser behavior,
1114 // probably opening in a new window or tab.
1115 const target
= event
.target
;
1116 if (target
.tagName
!== "A" ||
1117 target
.parentElement
.id
!== HELP_BUTTON_ID
||
1123 event
.preventDefault();
1124 const menu
= getHelpMenu(true);
1125 const shouldShowHelp
= menu
.style
.display
=== "none";
1126 if (shouldShowHelp
) {
1129 window
.hidePopoverMenus();
1138 window
.addEventListener("hashchange", onHashChange
);
1139 searchState
.setup();
1143 let reset_button_timeout
= null;
1145 window
.copy_path
= but
=> {
1146 const parent
= but
.parentElement
;
1149 onEach(parent
.childNodes
, child
=> {
1150 if (child
.tagName
=== "A") {
1151 path
.push(child
.textContent
);
1155 const el
= document
.createElement("textarea");
1156 el
.value
= path
.join("::");
1157 el
.setAttribute("readonly", "");
1158 // To not make it appear on the screen.
1159 el
.style
.position
= "absolute";
1160 el
.style
.left
= "-9999px";
1162 document
.body
.appendChild(el
);
1164 document
.execCommand("copy");
1165 document
.body
.removeChild(el
);
1167 // There is always one children, but multiple childNodes.
1168 but
.children
[0].style
.display
= "none";
1171 if (but
.childNodes
.length
< 2) {
1172 tmp
= document
.createTextNode("✓");
1173 but
.appendChild(tmp
);
1175 onEachLazy(but
.childNodes
, e
=> {
1176 if (e
.nodeType
=== Node
.TEXT_NODE
) {
1181 tmp
.textContent
= "✓";
1184 if (reset_button_timeout
!== null) {
1185 window
.clearTimeout(reset_button_timeout
);
1188 function reset_button() {
1189 tmp
.textContent
= "";
1190 reset_button_timeout
= null;
1191 but
.children
[0].style
.display
= "";
1194 reset_button_timeout
= window
.setTimeout(reset_button
, 1000);