]> git.proxmox.com Git - rustc.git/blob - src/librustdoc/html/static/js/main.js
New upstream version 1.68.2+dfsg1
[rustc.git] / src / librustdoc / html / static / js / main.js
1 // Local js definitions:
2 /* global addClass, getSettingValue, hasClass, searchState */
3 /* global onEach, onEachLazy, removeClass */
4
5 "use strict";
6
7 // Get a value from the rustdoc-vars div, which is used to convey data from
8 // Rust to the JS. If there is no such element, return null.
9 function getVar(name) {
10 const el = document.getElementById("rustdoc-vars");
11 if (el) {
12 return el.attributes["data-" + name].value;
13 } else {
14 return null;
15 }
16 }
17
18 // Given a basename (e.g. "storage") and an extension (e.g. ".js"), return a URL
19 // for a resource under the root-path, with the resource-suffix.
20 function resourcePath(basename, extension) {
21 return getVar("root-path") + basename + getVar("resource-suffix") + extension;
22 }
23
24 function hideMain() {
25 addClass(document.getElementById(MAIN_ID), "hidden");
26 }
27
28 function showMain() {
29 removeClass(document.getElementById(MAIN_ID), "hidden");
30 }
31
32 function elemIsInParent(elem, parent) {
33 while (elem && elem !== document.body) {
34 if (elem === parent) {
35 return true;
36 }
37 elem = elem.parentElement;
38 }
39 return false;
40 }
41
42 function blurHandler(event, parentElem, hideCallback) {
43 if (!elemIsInParent(document.activeElement, parentElem) &&
44 !elemIsInParent(event.relatedTarget, parentElem)
45 ) {
46 hideCallback();
47 }
48 }
49
50 window.rootPath = getVar("root-path");
51 window.currentCrate = getVar("current-crate");
52
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;
60 }
61 }
62
63 // Gets the human-readable string for the virtual-key code of the
64 // given KeyboardEvent, ev.
65 //
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.
71 //
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") {
75 return ev.key;
76 }
77
78 const c = ev.charCode || ev.keyCode;
79 if (c === 27) {
80 return "Escape";
81 }
82 return String.fromCharCode(c);
83 }
84
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";
90
91 function getSettingsButton() {
92 return document.getElementById(SETTINGS_BUTTON_ID);
93 }
94
95 function getHelpButton() {
96 return document.getElementById(HELP_BUTTON_ID);
97 }
98
99 // Returns the current URL without any query parameter or hash.
100 function getNakedUrl() {
101 return window.location.href.split("?")[0].split("#")[0];
102 }
103
104 /**
105 * This function inserts `newNode` after `referenceNode`. It doesn't work if `referenceNode`
106 * doesn't have a parent node.
107 *
108 * @param {HTMLElement} newNode
109 * @param {HTMLElement} referenceNode
110 */
111 function insertAfter(newNode, referenceNode) {
112 referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
113 }
114
115 /**
116 * This function creates a new `<section>` with the given `id` and `classes` if it doesn't already
117 * exist.
118 *
119 * More information about this in `switchDisplayedElement` documentation.
120 *
121 * @param {string} id
122 * @param {string} classes
123 */
124 function getOrCreateSection(id, classes) {
125 let el = document.getElementById(id);
126
127 if (!el) {
128 el = document.createElement("section");
129 el.id = id;
130 el.className = classes;
131 insertAfter(el, document.getElementById(MAIN_ID));
132 }
133 return el;
134 }
135
136 /**
137 * Returns the `<section>` element which contains the displayed element.
138 *
139 * @return {HTMLElement}
140 */
141 function getAlternativeDisplayElem() {
142 return getOrCreateSection(ALTERNATIVE_DISPLAY_ID, "content hidden");
143 }
144
145 /**
146 * Returns the `<section>` element which contains the not-displayed elements.
147 *
148 * @return {HTMLElement}
149 */
150 function getNotDisplayedElem() {
151 return getOrCreateSection(NOT_DISPLAYED_ID, "hidden");
152 }
153
154 /**
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.
160 *
161 * @param {HTMLElement} elemToDisplay
162 */
163 function switchDisplayedElement(elemToDisplay) {
164 const el = getAlternativeDisplayElem();
165
166 if (el.children.length > 0) {
167 getNotDisplayedElem().appendChild(el.firstElementChild);
168 }
169 if (elemToDisplay === null) {
170 addClass(el, "hidden");
171 showMain();
172 return;
173 }
174 el.appendChild(elemToDisplay);
175 hideMain();
176 removeClass(el, "hidden");
177 }
178
179 function browserSupportsHistoryApi() {
180 return window.history && typeof window.history.pushState === "function";
181 }
182
183 // eslint-disable-next-line no-unused-vars
184 function loadCss(cssUrl) {
185 const link = document.createElement("link");
186 link.href = cssUrl;
187 link.rel = "stylesheet";
188 document.getElementsByTagName("head")[0].appendChild(link);
189 }
190
191 (function() {
192 const isHelpPage = window.location.pathname.endsWith("/help.html");
193
194 function loadScript(url) {
195 const script = document.createElement("script");
196 script.src = url;
197 document.head.append(script);
198 }
199
200 getSettingsButton().onclick = event => {
201 if (event.ctrlKey || event.altKey || event.metaKey) {
202 return;
203 }
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"));
211 };
212
213 window.searchState = {
214 loadingText: "Loading search results...",
215 input: document.getElementsByClassName("search-input")[0],
216 outputElement: () => {
217 let el = document.getElementById("search");
218 if (!el) {
219 el = document.createElement("section");
220 el.id = "search";
221 getNotDisplayedElem().appendChild(el);
222 }
223 return el;
224 },
225 title: document.title,
226 titleBeforeSearch: document.title,
227 timeout: null,
228 // On the search screen, so you remain on the last tab you opened.
229 //
230 // 0 for "In Names"
231 // 1 for "In Parameters"
232 // 2 for "In Return Types"
233 currentTab: 0,
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;
240 }
241 },
242 isDisplayed: () => searchState.outputElement().parentElement.id === ALTERNATIVE_DISPLAY_ID,
243 // Sets the focus on the search bar at the top of the page
244 focus: () => {
245 searchState.input.focus();
246 },
247 // Removes the focus from the search bar.
248 defocus: () => {
249 searchState.input.blur();
250 },
251 showResults: search => {
252 if (search === null || typeof search === "undefined") {
253 search = searchState.outputElement();
254 }
255 switchDisplayedElement(search);
256 searchState.mouseMovedAfterSearch = false;
257 document.title = searchState.title;
258 },
259 hideResults: () => {
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);
266 }
267 },
268 getQueryStringParams: () => {
269 const params = {};
270 window.location.search.substring(1).split("&").
271 map(s => {
272 const pair = s.split("=");
273 params[decodeURIComponent(pair[0])] =
274 typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]);
275 });
276 return params;
277 },
278 setup: () => {
279 const search_input = searchState.input;
280 if (!searchState.input) {
281 return;
282 }
283 let searchLoaded = false;
284 function loadSearch() {
285 if (!searchLoaded) {
286 searchLoaded = true;
287 loadScript(getVar("static-root-path") + getVar("search-js"));
288 loadScript(resourcePath("search-index", ".js"));
289 }
290 }
291
292 search_input.addEventListener("focus", () => {
293 search_input.origPlaceholder = search_input.placeholder;
294 search_input.placeholder = "Type your search here.";
295 loadSearch();
296 });
297
298 if (search_input.value !== "") {
299 loadSearch();
300 }
301
302 const params = searchState.getQueryStringParams();
303 if (params.search !== undefined) {
304 searchState.setLoadingSearch();
305 loadSearch();
306 }
307 },
308 setLoadingSearch: () => {
309 const search = searchState.outputElement();
310 search.innerHTML = "<h3 class=\"search-loading\">" + searchState.loadingText + "</h3>";
311 searchState.showResults(search);
312 },
313 };
314
315 function getPageId() {
316 if (window.location.hash) {
317 const tmp = window.location.hash.replace(/^#/, "");
318 if (tmp.length > 0) {
319 return tmp;
320 }
321 }
322 return null;
323 }
324
325 const toggleAllDocsId = "toggle-all-docs";
326 let savedHash = "";
327
328 function handleHashes(ev) {
329 if (ev !== null && searchState.isDisplayed() && ev.newURL) {
330 // This block occurs when clicking on an element in the navbar while
331 // in a search.
332 switchDisplayedElement(null);
333 const hash = ev.newURL.slice(ev.newURL.indexOf("#") + 1);
334 if (browserSupportsHistoryApi()) {
335 // `window.location.search`` contains all the query parameters, not just `search`.
336 history.replaceState(null, "",
337 getNakedUrl() + window.location.search + "#" + hash);
338 }
339 const elem = document.getElementById(hash);
340 if (elem) {
341 elem.scrollIntoView();
342 }
343 }
344 // This part is used in case an element is not visible.
345 if (savedHash !== window.location.hash) {
346 savedHash = window.location.hash;
347 if (savedHash.length === 0) {
348 return;
349 }
350 expandSection(savedHash.slice(1)); // we remove the '#'
351 }
352 }
353
354 function onHashChange(ev) {
355 // If we're in mobile mode, we should hide the sidebar in any case.
356 hideSidebar();
357 handleHashes(ev);
358 }
359
360 function openParentDetails(elem) {
361 while (elem) {
362 if (elem.tagName === "DETAILS") {
363 elem.open = true;
364 }
365 elem = elem.parentNode;
366 }
367 }
368
369 function expandSection(id) {
370 openParentDetails(document.getElementById(id));
371 }
372
373 function handleEscape(ev) {
374 searchState.clearInputTimeout();
375 switchDisplayedElement(null);
376 if (browserSupportsHistoryApi()) {
377 history.replaceState(null, window.currentCrate + " - Rust",
378 getNakedUrl() + window.location.hash);
379 }
380 ev.preventDefault();
381 searchState.defocus();
382 window.hideAllModals(true); // true = reset focus for notable traits
383 }
384
385 function handleShortcut(ev) {
386 // Don't interfere with browser shortcuts
387 const disableShortcuts = getSettingValue("disable-shortcuts") === "true";
388 if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) {
389 return;
390 }
391
392 if (document.activeElement.tagName === "INPUT" &&
393 document.activeElement.type !== "checkbox" &&
394 document.activeElement.type !== "radio") {
395 switch (getVirtualKey(ev)) {
396 case "Escape":
397 handleEscape(ev);
398 break;
399 }
400 } else {
401 switch (getVirtualKey(ev)) {
402 case "Escape":
403 handleEscape(ev);
404 break;
405
406 case "s":
407 case "S":
408 ev.preventDefault();
409 searchState.focus();
410 break;
411
412 case "+":
413 ev.preventDefault();
414 expandAllDocs();
415 break;
416 case "-":
417 ev.preventDefault();
418 collapseAllDocs();
419 break;
420
421 case "?":
422 showHelp();
423 break;
424
425 default:
426 break;
427 }
428 }
429 }
430
431 document.addEventListener("keypress", handleShortcut);
432 document.addEventListener("keydown", handleShortcut);
433
434 function addSidebarItems() {
435 if (!window.SIDEBAR_ITEMS) {
436 return;
437 }
438 const sidebar = document.getElementsByClassName("sidebar-elems")[0];
439
440 /**
441 * Append to the sidebar a "block" of links - a heading along with a list (`<ul>`) of items.
442 *
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".
447 */
448 function block(shortty, id, longty) {
449 const filtered = window.SIDEBAR_ITEMS[shortty];
450 if (!filtered) {
451 return;
452 }
453
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;
458
459 for (const item of filtered) {
460 const name = item[0];
461 const desc = item[1]; // can be null
462
463 let path;
464 if (shortty === "mod") {
465 path = name + "/index.html";
466 } else {
467 path = shortty + "." + name + ".html";
468 }
469 const current_page = document.location.href.split("/").pop();
470 const link = document.createElement("a");
471 link.href = path;
472 link.title = desc;
473 if (path === current_page) {
474 link.className = "current";
475 }
476 link.textContent = name;
477 const li = document.createElement("li");
478 li.appendChild(link);
479 ul.appendChild(li);
480 }
481 sidebar.appendChild(h3);
482 sidebar.appendChild(ul);
483 }
484
485 if (sidebar) {
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");
500 }
501 }
502
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();
507
508 const TEXT_IDX = 0;
509 const SYNTHETIC_IDX = 1;
510 const TYPES_IDX = 2;
511
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.
515 //
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");
520 if (!aliases) {
521 return;
522 }
523 aliases.split(",").forEach(alias => {
524 inlined_types.add(alias);
525 });
526 });
527 }
528
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) {
541 continue;
542 }
543 const structs = imp[lib];
544
545 struct_loop:
546 for (const struct of structs) {
547 const list = struct[SYNTHETIC_IDX] ? synthetic_implementors : implementors;
548
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;
555 }
556 inlined_types.add(struct_type);
557 }
558 }
559
560 const code = document.createElement("h3");
561 code.innerHTML = struct[TEXT_IDX];
562 addClass(code, "code-header");
563
564 onEachLazy(code.getElementsByTagName("a"), elem => {
565 const href = elem.getAttribute("href");
566
567 if (href && !/^(?:[a-z+]+:)?\/\//.test(href)) {
568 elem.setAttribute("href", window.rootPath + href);
569 }
570 });
571
572 const currentId = baseIdName + currentNbImpls;
573 const anchor = document.createElement("a");
574 anchor.href = "#" + currentId;
575 addClass(anchor, "anchor");
576
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);
583 currentNbImpls += 1;
584 }
585 }
586 };
587 if (window.pending_implementors) {
588 window.register_implementors(window.pending_implementors);
589 }
590
591 function addSidebarCrates() {
592 if (!window.ALL_CRATES) {
593 return;
594 }
595 const sidebarElems = document.getElementsByClassName("sidebar-elems")[0];
596 if (!sidebarElems) {
597 return;
598 }
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";
604
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";
610 }
611 link.textContent = crate;
612
613 const li = document.createElement("li");
614 li.appendChild(link);
615 ul.appendChild(li);
616 }
617 sidebarElems.appendChild(h3);
618 sidebarElems.appendChild(ul);
619 }
620
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")) {
626 e.open = true;
627 }
628 });
629 innerToggle.title = "collapse all docs";
630 innerToggle.children[0].innerText = "\u2212"; // "\u2212" is "−" minus sign
631 }
632
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"))
640 ) {
641 e.open = false;
642 }
643 });
644 innerToggle.title = "expand all docs";
645 innerToggle.children[0].innerText = "+";
646 }
647
648 function toggleAllDocs() {
649 const innerToggle = document.getElementById(toggleAllDocsId);
650 if (!innerToggle) {
651 return;
652 }
653 if (hasClass(innerToggle, "will-expand")) {
654 expandAllDocs();
655 } else {
656 collapseAllDocs();
657 }
658 }
659
660 (function() {
661 const toggles = document.getElementById(toggleAllDocsId);
662 if (toggles) {
663 toggles.onclick = toggleAllDocs;
664 }
665
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";
669
670 function setImplementorsTogglesOpen(id, open) {
671 const list = document.getElementById(id);
672 if (list !== null) {
673 onEachLazy(list.getElementsByClassName("implementors-toggle"), e => {
674 e.open = open;
675 });
676 }
677 }
678
679 if (hideImplementations) {
680 setImplementorsTogglesOpen("trait-implementations-list", false);
681 setImplementorsTogglesOpen("blanket-implementations-list", false);
682 }
683
684 onEachLazy(document.getElementsByClassName("toggle"), e => {
685 if (!hideLargeItemContents && hasClass(e, "type-contents-toggle")) {
686 e.open = true;
687 }
688 if (hideMethodDocs && hasClass(e, "method-toggle")) {
689 e.open = false;
690 }
691
692 });
693
694 const pageId = getPageId();
695 if (pageId !== null) {
696 expandSection(pageId);
697 }
698 }());
699
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) {
705 return;
706 }
707 const count = x.textContent.split("\n").length;
708 const elems = [];
709 for (let i = 0; i < count; ++i) {
710 elems.push(i + 1);
711 }
712 const node = document.createElement("pre");
713 addClass(node, "example-line-numbers");
714 node.innerHTML = elems.join("\n");
715 parent.insertBefore(node, x);
716 });
717 };
718
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);
725 }
726 });
727 };
728
729 if (getSettingValue("line-numbers") === "true") {
730 window.rustdoc_add_line_numbers_to_examples();
731 }
732
733 let oldSidebarScrollPosition = null;
734
735 // Scroll locking used both here and in source-script.js
736
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`;
745 if (mobile_topbar) {
746 mobile_topbar.style.top = `${oldSidebarScrollPosition}px`;
747 mobile_topbar.style.position = "relative";
748 }
749 } else {
750 oldSidebarScrollPosition = null;
751 }
752 };
753
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 = "";
761 if (mobile_topbar) {
762 mobile_topbar.style.top = "";
763 mobile_topbar.style.position = "";
764 }
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;
769 }
770 };
771
772 function showSidebar() {
773 window.hideAllModals(false);
774 window.rustdocMobileScrollLock();
775 const sidebar = document.getElementsByClassName("sidebar")[0];
776 addClass(sidebar, "shown");
777 }
778
779 function hideSidebar() {
780 window.rustdocMobileScrollUnlock();
781 const sidebar = document.getElementsByClassName("sidebar")[0];
782 removeClass(sidebar, "shown");
783 }
784
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.
790 hideSidebar();
791 }
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.
795 //
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;
799 hideNotable(false);
800 if (force_visible) {
801 showNotable(base);
802 base.NOTABLE_FORCE_VISIBLE = true;
803 }
804 }
805 });
806
807 const mainElem = document.getElementById(MAIN_ID);
808 if (mainElem) {
809 mainElem.addEventListener("click", hideSidebar);
810 }
811
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));
818 hideSidebar();
819 });
820 });
821
822 onEachLazy(document.querySelectorAll(".toggle > summary:not(.hideme)"), el => {
823 el.addEventListener("click", e => {
824 if (e.target.tagName !== "SUMMARY" && e.target.tagName !== "A") {
825 e.preventDefault();
826 }
827 });
828 });
829
830 function showNotable(e) {
831 if (!window.NOTABLE_TRAITS) {
832 const data = document.getElementById("notable-traits-data");
833 if (data) {
834 window.NOTABLE_TRAITS = JSON.parse(data.innerText);
835 } else {
836 throw new Error("showNotable() called on page without any notable traits!");
837 }
838 }
839 if (window.CURRENT_NOTABLE_ELEMENT && window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE === e) {
840 // Make this function idempotent.
841 return;
842 }
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;
863 if (finalPos > 0) {
864 wrapper.style.left = finalPos + "px";
865 } else {
866 wrapper.style.setProperty(
867 "--popover-arrow-offset",
868 (wrapperPos.right - pos.right + 4) + "px"
869 );
870 }
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") {
877 return;
878 }
879 if (!e.NOTABLE_FORCE_VISIBLE && !elemIsInParent(event.relatedTarget, e)) {
880 hideNotable(true);
881 }
882 };
883 }
884
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)
891 ) {
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.
896 //
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);
901 }
902 }
903
904 function hideNotable(focus) {
905 if (window.CURRENT_NOTABLE_ELEMENT) {
906 if (window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.NOTABLE_FORCE_VISIBLE) {
907 if (focus) {
908 window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.focus();
909 }
910 window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.NOTABLE_FORCE_VISIBLE = false;
911 }
912 const body = document.getElementsByTagName("body")[0];
913 body.removeChild(window.CURRENT_NOTABLE_ELEMENT);
914 window.CURRENT_NOTABLE_ELEMENT = null;
915 }
916 }
917
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) {
922 hideNotable(true);
923 } else {
924 showNotable(this);
925 window.CURRENT_NOTABLE_ELEMENT.setAttribute("tabindex", "0");
926 window.CURRENT_NOTABLE_ELEMENT.focus();
927 window.CURRENT_NOTABLE_ELEMENT.onblur = notableBlurHandler;
928 }
929 return false;
930 };
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") {
934 return;
935 }
936 showNotable(this);
937 };
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") {
941 return;
942 }
943 if (!this.NOTABLE_FORCE_VISIBLE &&
944 !elemIsInParent(ev.relatedTarget, window.CURRENT_NOTABLE_ELEMENT)) {
945 hideNotable(true);
946 }
947 };
948 });
949
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")) {
955 showSidebar();
956 } else {
957 hideSidebar();
958 }
959 });
960 }
961
962 function helpBlurHandler(event) {
963 blurHandler(event, getHelpButton(), window.hidePopoverMenus);
964 }
965
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>.";
971
972 const shortcuts = [
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 ["&#9166;", "Go to active search result"],
979 ["+", "Expand all sections"],
980 ["-", "Collapse all sections"],
981 ].map(x => "<dt>" +
982 x[0].split(" ")
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>";
988
989 const infos = [
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 -&gt; usize</code> or \
996 <code>-&gt; 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;
1006
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);
1012
1013 const container = document.createElement("div");
1014 if (!isHelpPage) {
1015 container.className = "popover";
1016 }
1017 container.id = "help";
1018 container.style.display = "none";
1019
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);
1024
1025 container.appendChild(book_info);
1026 container.appendChild(side_by_side);
1027 container.appendChild(rustdoc_version);
1028
1029 if (isHelpPage) {
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";
1034 } else {
1035 const help_button = getHelpButton();
1036 help_button.appendChild(container);
1037
1038 container.onblur = helpBlurHandler;
1039 help_button.onblur = helpBlurHandler;
1040 help_button.children[0].onblur = helpBlurHandler;
1041 }
1042
1043 return container;
1044 }
1045
1046 /**
1047 * Hide popover menus, notable trait tooltips, and the sidebar (if applicable).
1048 *
1049 * Pass "true" to reset focus for notable traits.
1050 */
1051 window.hideAllModals = function(switchFocus) {
1052 hideSidebar();
1053 window.hidePopoverMenus();
1054 hideNotable(switchFocus);
1055 };
1056
1057 /**
1058 * Hide all the popover menus.
1059 */
1060 window.hidePopoverMenus = function() {
1061 onEachLazy(document.querySelectorAll(".search-form .popover"), elem => {
1062 elem.style.display = "none";
1063 });
1064 };
1065
1066 /**
1067 * Returns the help menu element (not the button).
1068 *
1069 * @param {boolean} buildNeeded - If this argument is `false`, the help menu element won't be
1070 * built if it doesn't exist.
1071 *
1072 * @return {HTMLElement}
1073 */
1074 function getHelpMenu(buildNeeded) {
1075 let menu = getHelpButton().querySelector(".popover");
1076 if (!menu && buildNeeded) {
1077 menu = buildHelpMenu();
1078 }
1079 return menu;
1080 }
1081
1082 /**
1083 * Show the help popup menu.
1084 */
1085 function showHelp() {
1086 // Prevent `blur` events from being dispatched as a result of closing
1087 // other modals.
1088 getHelpButton().querySelector("a").focus();
1089 const menu = getHelpMenu(true);
1090 if (menu.style.display === "none") {
1091 window.hideAllModals();
1092 menu.style.display = "";
1093 }
1094 }
1095
1096 if (isHelpPage) {
1097 showHelp();
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 ||
1103 event.ctrlKey ||
1104 event.altKey ||
1105 event.metaKey) {
1106 return;
1107 }
1108 event.preventDefault();
1109 });
1110 } else {
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 ||
1118 event.ctrlKey ||
1119 event.altKey ||
1120 event.metaKey) {
1121 return;
1122 }
1123 event.preventDefault();
1124 const menu = getHelpMenu(true);
1125 const shouldShowHelp = menu.style.display === "none";
1126 if (shouldShowHelp) {
1127 showHelp();
1128 } else {
1129 window.hidePopoverMenus();
1130 }
1131 });
1132 }
1133
1134 setMobileTopbar();
1135 addSidebarItems();
1136 addSidebarCrates();
1137 onHashChange(null);
1138 window.addEventListener("hashchange", onHashChange);
1139 searchState.setup();
1140 }());
1141
1142 (function() {
1143 let reset_button_timeout = null;
1144
1145 window.copy_path = but => {
1146 const parent = but.parentElement;
1147 const path = [];
1148
1149 onEach(parent.childNodes, child => {
1150 if (child.tagName === "A") {
1151 path.push(child.textContent);
1152 }
1153 });
1154
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";
1161
1162 document.body.appendChild(el);
1163 el.select();
1164 document.execCommand("copy");
1165 document.body.removeChild(el);
1166
1167 // There is always one children, but multiple childNodes.
1168 but.children[0].style.display = "none";
1169
1170 let tmp;
1171 if (but.childNodes.length < 2) {
1172 tmp = document.createTextNode("✓");
1173 but.appendChild(tmp);
1174 } else {
1175 onEachLazy(but.childNodes, e => {
1176 if (e.nodeType === Node.TEXT_NODE) {
1177 tmp = e;
1178 return true;
1179 }
1180 });
1181 tmp.textContent = "✓";
1182 }
1183
1184 if (reset_button_timeout !== null) {
1185 window.clearTimeout(reset_button_timeout);
1186 }
1187
1188 function reset_button() {
1189 tmp.textContent = "";
1190 reset_button_timeout = null;
1191 but.children[0].style.display = "";
1192 }
1193
1194 reset_button_timeout = window.setTimeout(reset_button, 1000);
1195 };
1196 }());