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