about summary refs log tree commit diff
path: root/src/librustdoc/html/static
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/html/static')
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css76
-rw-r--r--src/librustdoc/html/static/css/themes/ayu.css67
-rw-r--r--src/librustdoc/html/static/css/themes/dark.css80
-rw-r--r--src/librustdoc/html/static/css/themes/light.css82
-rw-r--r--src/librustdoc/html/static/js/externs.js59
-rw-r--r--src/librustdoc/html/static/js/main.js42
-rw-r--r--src/librustdoc/html/static/js/search.js163
-rw-r--r--src/librustdoc/html/static/js/source-script.js15
8 files changed, 294 insertions, 290 deletions
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 5d0756d30fb..f7b4fdb736c 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -114,6 +114,9 @@ body {
 	-webkit-font-feature-settings: "kern", "liga";
 	-moz-font-feature-settings: "kern", "liga";
 	font-feature-settings: "kern", "liga";
+
+	background-color: var(--main-background-color);
+	color: var(--main-color);
 }
 
 h1 {
@@ -158,8 +161,8 @@ h1.fqn {
 	Underlines elsewhere in the documentation break up visual flow and tend to invert
 	section hierarchies. */
 h2,
-.top-doc h3,
-.top-doc h4 {
+.top-doc .docblock > h3,
+.top-doc .docblock > h4 {
 	border-bottom: 1px solid;
 }
 h3.code-header {
@@ -214,6 +217,26 @@ a.srclink,
 	font-family: "Fira Sans", Arial, NanumBarunGothic, sans-serif;
 }
 
+h1, h2, h3, h4,
+a#toggle-all-docs,
+a.anchor,
+.small-section-header a,
+#source-sidebar a,
+pre.rust a,
+.sidebar h2 a,
+.sidebar h3 a,
+.mobile-topbar h2 a,
+.in-band a,
+.search-results a,
+.module-item .stab,
+.import-item .stab,
+.result-name .primitive > i, .result-name .keyword > i,
+.content .method .where,
+.content .fn .where,
+.content .where.fmt-newline {
+	color: var(--main-color);
+}
+
 ol, ul {
 	padding-left: 24px;
 }
@@ -391,6 +414,14 @@ nav.sub {
 	display: none;
 }
 
+.source .sidebar, #sidebar-toggle, #source-sidebar {
+	background-color: var(--sidebar-background-color);
+}
+
+#sidebar-toggle:hover {
+	background-color: var(--sidebar-background-color-hover);
+}
+
 .source .sidebar > *:not(#sidebar-toggle) {
 	opacity: 0;
 	visibility: hidden;
@@ -629,11 +660,6 @@ h2.location a {
 	display: block;
 }
 
-.invisible {
-	width: 100%;
-	display: inline-block;
-}
-
 .content .in-band {
 	flex-grow: 1;
 	margin: 0px;
@@ -1008,6 +1034,11 @@ table,
 	top: -5px;
 }
 
+.popover, .popover::before {
+	background-color: var(--main-background-color);
+	color: var(--main-color);
+}
+
 #help-button .popover {
 	max-width: 600px;
 }
@@ -1428,6 +1459,25 @@ pre.rust {
 	animation: rotating 2s linear infinite;
 }
 
+.setting-line .radio-line input:checked {
+	box-shadow: inset 0 0 0 3px var(--main-background-color);
+	background-color: var(--settings-input-color);
+}
+.setting-line .radio-line input:focus {
+	box-shadow: 0 0 1px 1px var(--settings-input-color);
+}
+/* In here we combine both `:focus` and `:checked` properties. */
+.setting-line .radio-line input:checked:focus {
+	box-shadow: inset 0 0 0 3px var(--main-background-color),
+		0 0 2px 2px var(--settings-input-color);
+}
+.setting-line .radio-line input:hover {
+	border-color: var(--settings-input-color) !important;
+}
+input:checked + .slider {
+	background-color: var(--settings-input-color);
+}
+
 #help-button > button {
 	font-family: "Fira Sans", Arial, sans-serif;
 	text-align: center;
@@ -1681,6 +1731,11 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	content: "Collapse";
 }
 
+/* This is needed in docblocks to have the "▶" element to be on the same line. */
+.docblock summary > * {
+	display: inline-block;
+}
+
 /* Media Queries */
 
 @media (min-width: 701px) {
@@ -1772,9 +1827,11 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	/* The source view uses a different design for the sidebar toggle, and doesn't have a topbar,
 	   so don't bump down the main content or the sidebar. */
 	.source main,
-	.source .sidebar {
+	.rustdoc.source .sidebar {
 		top: 0;
 		padding: 0;
+		height: 100vh;
+		border: 0;
 	}
 
 	.sidebar.shown,
@@ -1924,6 +1981,9 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 		width: unset;
 		border-top-right-radius: unset;
 		border-bottom-right-radius: unset;
+		position: sticky;
+		border: 0;
+		border-bottom: 1px solid;
 	}
 
 	#source-sidebar {
diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css
index b7d0db1f002..7756e877ef7 100644
--- a/src/librustdoc/html/static/css/themes/ayu.css
+++ b/src/librustdoc/html/static/css/themes/ayu.css
@@ -3,30 +3,12 @@ Based off of the Ayu theme
 Original by Dempfi (https://github.com/dempfi/ayu)
 */
 
-/* General structure and fonts */
-
-body, .popover, .popover::before {
-	background-color: #0f1419;
-	color: #c5c5c5;
-}
-
-.setting-line .radio-line input {
-	border-color: #c5c5c5;
-}
-.setting-line .radio-line input:checked {
-	box-shadow: inset 0 0 0 3px #0f1419;
-	background-color: #ffb454;
-}
-.setting-line .radio-line input:focus {
-	box-shadow: 0 0 1px 1px #ffb454;
-}
-/* In here we combine both `:focus` and `:checked` properties. */
-.setting-line .radio-line input:checked:focus {
-	box-shadow: inset 0 0 0 3px 0f1419,
-		0 0 2px 2px #ffb454;
-}
-.setting-line .radio-line input:hover {
-	border-color: #ffb454 !important;
+:root {
+	--main-background-color: #0f1419;
+	--main-color: #c5c5c5;
+	--settings-input-color: #ffb454;
+	--sidebar-background-color: #14191f;
+	--sidebar-background-color-hover: rgba(70, 70, 70, 0.33);
 }
 
 .slider {
@@ -35,9 +17,6 @@ body, .popover, .popover::before {
 .slider:before {
 	background-color: white;
 }
-input:checked + .slider {
-	background-color: #ffb454;
-}
 input:focus + .slider {
 	box-shadow: 0 0 0 2px #0a84ff, 0 0 0 6px rgba(10, 132, 255, 0.3);
 }
@@ -62,10 +41,6 @@ h4 {
 	background-color: #0f1419;
 }
 
-.invisible {
-	background: rgba(0, 0, 0, 0);
-}
-
 .docblock code {
 	color: #ffb454;
 }
@@ -129,10 +104,6 @@ pre, .rustdoc.source .example-wrap {
 	color: #ffb44c;
 }
 
-.source .sidebar {
-	background-color: #14191f;
-}
-
 .sidebar-elems .location {
 	color: #ff7733;
 }
@@ -153,12 +124,6 @@ pre, .rustdoc.source .example-wrap {
 	border-color: #5c6773;
 }
 
-.content .method .where,
-.content .fn .where,
-.content .where.fmt-newline {
-	color: #c5c5c5;
-}
-
 .search-results a:hover {
 	background-color: #777;
 }
@@ -233,17 +198,6 @@ a {
 	color: #39AFD7;
 }
 
-a#toggle-all-docs,
-a.anchor,
-.small-section-header a,
-#source-sidebar a,
-pre.rust a,
-.sidebar h2 a,
-.sidebar h3 a,
-.mobile-topbar h2 a,
-.in-band a {
-	color: #c5c5c5;
-}
 .sidebar h2 a,
 .sidebar h3 a {
 	color: white;
@@ -617,15 +571,6 @@ kbd {
 	color: #999;
 }
 
-#sidebar-toggle {
-	background-color: #14191f;
-}
-#sidebar-toggle:hover {
-	background-color: rgba(70, 70, 70, 0.33);
-}
-#source-sidebar {
-	background-color: #14191f;
-}
 #source-sidebar > .title {
 	color: #fff;
 	border-bottom-color: #5c6773;
diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css
index eb64ef3e771..04d5778f59c 100644
--- a/src/librustdoc/html/static/css/themes/dark.css
+++ b/src/librustdoc/html/static/css/themes/dark.css
@@ -1,25 +1,9 @@
-body, .popover, .popover::before {
-	background-color: #353535;
-	color: #ddd;
-}
-
-.setting-line .radio-line input {
-	border-color: #ddd;
-}
-.setting-line .radio-line input:checked {
-	box-shadow: inset 0 0 0 3px #353535;
-	background-color: #2196f3;
-}
-.setting-line .radio-line input:focus {
-	box-shadow: 0 0 1px 1px #2196f3;
-}
-/* In here we combine both `:focus` and `:checked` properties. */
-.setting-line .radio-line input:checked:focus {
-	box-shadow: inset 0 0 0 3px #353535,
-		0 0 2px 2px #2196f3;
-}
-.setting-line .radio-line input:hover {
-	border-color: #2196f3 !important;
+:root {
+	--main-background-color: #353535;
+	--main-color: #ddd;
+	--settings-input-color: #2196f3;
+	--sidebar-background-color: #565656;
+	--sidebar-background-color-hover: #676767;
 }
 
 .slider {
@@ -28,16 +12,10 @@ body, .popover, .popover::before {
 .slider:before {
 	background-color: white;
 }
-input:checked + .slider {
-	background-color: #2196F3;
-}
 input:focus + .slider {
 	box-shadow: 0 0 0 2px #0a84ff, 0 0 0 6px rgba(10, 132, 255, 0.3);
 }
 
-h1, h2, h3, h4 {
-	color: #ddd;
-}
 h1.fqn {
 	border-bottom-color: #d2d2d2;
 }
@@ -49,10 +27,6 @@ h2, h3, h4 {
 	background-color: #353535;
 }
 
-.invisible {
-	background: rgba(0, 0, 0, 0);
-}
-
 .docblock code, .docblock-short code {
 	background-color: #2A2A2A;
 }
@@ -98,10 +72,6 @@ pre, .rustdoc.source .example-wrap {
 	background: #444;
 }
 
-.source .sidebar {
-	background-color: #565656;
-}
-
 .line-numbers span { color: #3B91E2; }
 .line-numbers .line-highlighted {
 	background-color: #0a042f !important;
@@ -115,12 +85,6 @@ pre, .rustdoc.source .example-wrap {
 	border-color: #ddd;
 }
 
-.content .method .where,
-.content .fn .where,
-.content .where.fmt-newline {
-	color: #ddd;
-}
-
 .search-results a:hover {
 	background-color: #777;
 }
@@ -214,20 +178,6 @@ a {
 	color: #D2991D;
 }
 
-a#toggle-all-docs,
-a.anchor,
-.small-section-header a,
-#source-sidebar a,
-pre.rust a,
-.sidebar h2 a,
-.sidebar h3 a,
-.mobile-topbar h2 a,
-.in-band a {
-	color: #ddd;
-}
-.search-results a {
-	color: #ddd;
-}
 a.test-arrow {
 	color: #dedede;
 }
@@ -261,11 +211,6 @@ details.undocumented > summary::before {
 	border-color: #008dfd;
 }
 
-.module-item .stab,
-.import-item .stab {
-	color: #ddd;
-}
-
 .stab.empty-impl { background: #FFF5D6; border-color: #FFC600; color: #2f2f2f; }
 .stab.unstable { background: #FFF5D6; border-color: #FFC600; color: #2f2f2f; }
 .stab.deprecated { background: #ffc4c4; border-color: #db7b7b; color: #2f2f2f; }
@@ -291,10 +236,6 @@ details.undocumented > summary::before {
 	color: grey;
 }
 
-.result-name .primitive > i, .result-name .keyword > i {
-	color: #ddd;
-}
-
 .line-numbers :target { background-color: transparent; }
 
 /* Code highlighting */
@@ -488,15 +429,6 @@ kbd {
 	color: #ccc;
 }
 
-#sidebar-toggle {
-	background-color: #565656;
-}
-#sidebar-toggle:hover {
-	background-color: #676767;
-}
-#source-sidebar {
-	background-color: #565656;
-}
 #source-sidebar > .title {
 	border-bottom-color: #ccc;
 }
diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css
index 00cdf835897..5310736037a 100644
--- a/src/librustdoc/html/static/css/themes/light.css
+++ b/src/librustdoc/html/static/css/themes/light.css
@@ -1,27 +1,9 @@
-/* General structure and fonts */
-
-body, .popover, .popover::before {
-	background-color: white;
-	color: black;
-}
-
-.setting-line .radio-line input {
-	border-color: black;
-}
-.setting-line .radio-line input:checked {
-	box-shadow: inset 0 0 0 3px white;
-	background-color: #2196f3;
-}
-.setting-line .radio-line input:focus {
-	box-shadow: 0 0 1px 1px #2196f3;
-}
-/* In here we combine both `:focus` and `:checked` properties. */
-.setting-line .radio-line input:checked:focus {
-	box-shadow: inset 0 0 0 3px white,
-		0 0 2px 2px #2196f3;
-}
-.setting-line .radio-line input:hover {
-	border-color: #2196f3 !important;
+:root {
+	--main-background-color: white;
+	--main-color: black;
+	--settings-input-color: #2196f3;
+	--sidebar-background-color: #F5F5F5;
+	--sidebar-background-color-hover: #E0E0E0;
 }
 
 .slider {
@@ -30,16 +12,10 @@ body, .popover, .popover::before {
 .slider:before {
 	background-color: white;
 }
-input:checked + .slider {
-	background-color: #2196F3;
-}
 input:focus + .slider {
 	box-shadow: 0 0 0 2px #0a84ff, 0 0 0 6px rgba(10, 132, 255, 0.3);
 }
 
-h1, h2, h3, h4 {
-	color: black;
-}
 h1.fqn {
 	border-bottom-color: #DDDDDD;
 }
@@ -51,10 +27,6 @@ h2, h3, h4 {
 	background-color: white;
 }
 
-.invisible {
-	background: rgba(0, 0, 0, 0);
-}
-
 .docblock code, .docblock-short code {
 	background-color: #F5F5F5;
 }
@@ -100,10 +72,6 @@ pre, .rustdoc.source .example-wrap {
 	background-color: #fff;
 }
 
-.source .sidebar {
-	background-color: #f1f1f1;
-}
-
 .line-numbers span { color: #c67e2d; }
 .line-numbers .line-highlighted {
 	background-color: #FDFFD3 !important;
@@ -117,12 +85,6 @@ pre, .rustdoc.source .example-wrap {
 	border-color: #ddd;
 }
 
-.content .method .where,
-.content .fn .where,
-.content .where.fmt-newline {
-	color: #4E4C4C;
-}
-
 .search-results a:hover {
 	background-color: #ddd;
 }
@@ -213,20 +175,6 @@ a {
 	color: #3873AD;
 }
 
-a#toggle-all-docs,
-a.anchor,
-.small-section-header a,
-#source-sidebar a,
-pre.rust a,
-.sidebar h2 a,
-.sidebar h3 a,
-.mobile-topbar h2 a,
-.in-band a {
-	color: #000;
-}
-.search-results a {
-	color: initial;
-}
 a.test-arrow {
 	color: #f5f5f5;
 }
@@ -250,11 +198,6 @@ details.undocumented > summary::before {
 	border-color: #66afe9;
 }
 
-.module-item .stab,
-.import-item .stab {
-	color: #000;
-}
-
 .stab.empty-impl { background: #FFF5D6; border-color: #FFC600; }
 .stab.unstable { background: #FFF5D6; border-color: #FFC600; }
 .stab.deprecated { background: #ffc4c4; border-color: #db7b7b; }
@@ -275,10 +218,6 @@ details.undocumented > summary::before {
 	color: grey;
 }
 
-.result-name .primitive > i, .result-name .keyword > i {
-	color: black;
-}
-
 .line-numbers :target { background-color: transparent; }
 
 /* Code highlighting */
@@ -472,15 +411,6 @@ kbd {
 	color: #999;
 }
 
-#sidebar-toggle {
-	background-color: #F5F5F5;
-}
-#sidebar-toggle:hover {
-	background-color: #E0E0E0;
-}
-#source-sidebar {
-	background-color: #F5F5F5;
-}
 #source-sidebar > .title {
 	border-bottom-color: #ccc;
 }
diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js
index defdc20132e..ecbe15a59da 100644
--- a/src/librustdoc/html/static/js/externs.js
+++ b/src/librustdoc/html/static/js/externs.js
@@ -81,3 +81,62 @@ let ResultsTable;
  * }}
  */
 let Results;
+
+/**
+ * A pair of [inputs, outputs], or 0 for null. This is stored in the search index.
+ * The JavaScript deserializes this into FunctionSearchType.
+ *
+ * Numeric IDs are *ONE-indexed* into the paths array (`p`). Zero is used as a sentinel for `null`
+ * because `null` is four bytes while `0` is one byte.
+ *
+ * An input or output can be encoded as just a number if there is only one of them, AND
+ * it has no generics. The no generics rule exists to avoid ambiguity: imagine if you had
+ * a function with a single output, and that output had a single generic:
+ *
+ *     fn something() -> Result<usize, usize>
+ *
+ * If output was allowed to be any RawFunctionType, it would look like this
+ *
+ *     [[], [50, [3, 3]]]
+ *
+ * The problem is that the above output could be interpreted as either a type with ID 50 and two
+ * generics, or it could be interpreted as a pair of types, the first one with ID 50 and the second
+ * with ID 3 and a single generic parameter that is also ID 3. We avoid this ambiguity by choosing
+ * in favor of the pair of types interpretation. This is why the `(number|Array<RawFunctionType>)`
+ * is used instead of `(RawFunctionType|Array<RawFunctionType>)`.
+ *
+ * @typedef {(
+ *     0 |
+ *     [(number|Array<RawFunctionType>)] |
+ *     [(number|Array<RawFunctionType>), (number|Array<RawFunctionType>)]
+ * )}
+ */
+let RawFunctionSearchType;
+
+/**
+ * A single function input or output type. This is either a single path ID, or a pair of
+ * [path ID, generics].
+ *
+ * Numeric IDs are *ONE-indexed* into the paths array (`p`). Zero is used as a sentinel for `null`
+ * because `null` is four bytes while `0` is one byte.
+ *
+ * @typedef {number | [number, Array<RawFunctionType>]}
+ */
+let RawFunctionType;
+
+/**
+ * @typedef {{
+ *     inputs: Array<FunctionType>,
+ *     outputs: Array<FunctionType>,
+ * }}
+ */
+let FunctionSearchType;
+
+/**
+ * @typedef {{
+ *     name: (null|string),
+ *     ty: (null|number),
+ *     generics: Array<FunctionType>,
+ * }}
+ */
+let FunctionType;
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index 70dbfd44425..6658f07ce01 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -4,40 +4,6 @@
 
 "use strict";
 
-if (!String.prototype.startsWith) {
-    String.prototype.startsWith = function(searchString, position) {
-        position = position || 0;
-        return this.indexOf(searchString, position) === position;
-    };
-}
-if (!String.prototype.endsWith) {
-    String.prototype.endsWith = function(suffix, length) {
-        const l = length || this.length;
-        return this.indexOf(suffix, l - suffix.length) !== -1;
-    };
-}
-
-if (!DOMTokenList.prototype.add) {
-    DOMTokenList.prototype.add = function(className) {
-        if (className && !hasClass(this, className)) {
-            if (this.className && this.className.length > 0) {
-                this.className += " " + className;
-            } else {
-                this.className = className;
-            }
-        }
-    };
-}
-
-if (!DOMTokenList.prototype.remove) {
-    DOMTokenList.prototype.remove = function(className) {
-        if (className && this.className) {
-            this.className = (" " + this.className + " ").replace(" " + className + " ", " ")
-                                                         .trim();
-        }
-    };
-}
-
 // Get a value from the rustdoc-vars div, which is used to convey data from
 // Rust to the JS. If there is no such element, return null.
 function getVar(name) {
@@ -412,14 +378,15 @@ function loadCss(cssFileName) {
         window.hidePopoverMenus();
     }
 
-    const disableShortcuts = getSettingValue("disable-shortcuts") === "true";
     function handleShortcut(ev) {
         // Don't interfere with browser shortcuts
+        const disableShortcuts = getSettingValue("disable-shortcuts") === "true";
         if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) {
             return;
         }
 
-        if (document.activeElement.tagName === "INPUT") {
+        if (document.activeElement.tagName === "INPUT" &&
+            document.activeElement.type !== "checkbox") {
             switch (getVirtualKey(ev)) {
             case "Escape":
                 handleEscape(ev);
@@ -926,6 +893,7 @@ function loadCss(cssFileName) {
     function showHelp() {
         const menu = getHelpMenu(true);
         if (menu.style.display === "none") {
+            window.hidePopoverMenus();
             menu.style.display = "";
         }
     }
@@ -939,6 +907,8 @@ function loadCss(cssFileName) {
         const shouldShowHelp = menu.style.display === "none";
         if (shouldShowHelp) {
             showHelp();
+        } else {
+            window.hidePopoverMenus();
         }
     });
 
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index cb1609d4983..75c7bd45a29 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -114,10 +114,6 @@ function levenshtein(s1, s2) {
 function initSearch(rawSearchIndex) {
     const MAX_LEV_DISTANCE = 3;
     const MAX_RESULTS = 200;
-    const GENERICS_DATA = 2;
-    const NAME = 0;
-    const INPUTS_DATA = 0;
-    const OUTPUT_DATA = 1;
     const NO_TYPE_FILTER = -1;
     /**
      *  @type {Array<Row>}
@@ -895,21 +891,18 @@ function initSearch(rawSearchIndex) {
          * @return {integer}           - Returns the best match (if any) or `MAX_LEV_DISTANCE + 1`.
          */
         function checkGenerics(row, elem, defaultLev) {
-            if (row.length <= GENERICS_DATA || row[GENERICS_DATA].length === 0) {
-                return elem.generics.length === 0 ? defaultLev : MAX_LEV_DISTANCE + 1;
-            } else if (row[GENERICS_DATA].length > 0 && row[GENERICS_DATA][0][NAME] === "") {
-                if (row.length > GENERICS_DATA) {
-                    return checkGenerics(row[GENERICS_DATA][0], elem, defaultLev);
-                }
+            if (row.generics.length === 0) {
                 return elem.generics.length === 0 ? defaultLev : MAX_LEV_DISTANCE + 1;
+            } else if (row.generics.length > 0 && row.generics[0].name === null) {
+                return checkGenerics(row.generics[0], elem, defaultLev);
             }
             // The names match, but we need to be sure that all generics kinda
             // match as well.
             let elem_name;
-            if (elem.generics.length > 0 && row[GENERICS_DATA].length >= elem.generics.length) {
+            if (elem.generics.length > 0 && row.generics.length >= elem.generics.length) {
                 const elems = Object.create(null);
-                for (const entry of row[GENERICS_DATA]) {
-                    elem_name = entry[NAME];
+                for (const entry of row.generics) {
+                    elem_name = entry.name;
                     if (elem_name === "") {
                         // Pure generic, needs to check into it.
                         if (checkGenerics(entry, elem, MAX_LEV_DISTANCE + 1) !== 0) {
@@ -963,7 +956,7 @@ function initSearch(rawSearchIndex) {
           */
         function checkIfInGenerics(row, elem) {
             let lev = MAX_LEV_DISTANCE + 1;
-            for (const entry of row[GENERICS_DATA]) {
+            for (const entry of row.generics) {
                 lev = Math.min(checkType(entry, elem, true), lev);
                 if (lev === 0) {
                     break;
@@ -984,23 +977,22 @@ function initSearch(rawSearchIndex) {
           *                     no match, returns `MAX_LEV_DISTANCE + 1`.
           */
         function checkType(row, elem, literalSearch) {
-            if (row[NAME].length === 0) {
+            if (row.name === null) {
                 // This is a pure "generic" search, no need to run other checks.
-                if (row.length > GENERICS_DATA) {
+                if (row.generics.length > 0) {
                     return checkIfInGenerics(row, elem);
                 }
                 return MAX_LEV_DISTANCE + 1;
             }
 
-            let lev = levenshtein(row[NAME], elem.name);
+            let lev = levenshtein(row.name, elem.name);
             if (literalSearch) {
                 if (lev !== 0) {
                     // The name didn't match, let's try to check if the generics do.
                     if (elem.generics.length === 0) {
-                        const checkGeneric = (row.length > GENERICS_DATA &&
-                            row[GENERICS_DATA].length > 0);
-                        if (checkGeneric && row[GENERICS_DATA]
-                            .findIndex(tmp_elem => tmp_elem[NAME] === elem.name) !== -1) {
+                        const checkGeneric = row.generics.length > 0;
+                        if (checkGeneric && row.generics
+                            .findIndex(tmp_elem => tmp_elem.name === elem.name) !== -1) {
                             return 0;
                         }
                     }
@@ -1009,7 +1001,7 @@ function initSearch(rawSearchIndex) {
                     return checkGenerics(row, elem, MAX_LEV_DISTANCE + 1);
                 }
                 return 0;
-            } else if (row.length > GENERICS_DATA) {
+            } else if (row.generics.length > 0) {
                 if (elem.generics.length === 0) {
                     if (lev === 0) {
                         return 0;
@@ -1059,9 +1051,9 @@ function initSearch(rawSearchIndex) {
         function findArg(row, elem, typeFilter) {
             let lev = MAX_LEV_DISTANCE + 1;
 
-            if (row && row.type && row.type[INPUTS_DATA] && row.type[INPUTS_DATA].length > 0) {
-                for (const input of row.type[INPUTS_DATA]) {
-                    if (!typePassesFilter(typeFilter, input[1])) {
+            if (row && row.type && row.type.inputs && row.type.inputs.length > 0) {
+                for (const input of row.type.inputs) {
+                    if (!typePassesFilter(typeFilter, input.ty)) {
                         continue;
                     }
                     lev = Math.min(lev, checkType(input, elem, parsedQuery.literalSearch));
@@ -1086,13 +1078,10 @@ function initSearch(rawSearchIndex) {
         function checkReturned(row, elem, typeFilter) {
             let lev = MAX_LEV_DISTANCE + 1;
 
-            if (row && row.type && row.type.length > OUTPUT_DATA) {
-                let ret = row.type[OUTPUT_DATA];
-                if (typeof ret[0] === "string") {
-                    ret = [ret];
-                }
+            if (row && row.type && row.type.output.length > 0) {
+                const ret = row.type.output;
                 for (const ret_ty of ret) {
-                    if (!typePassesFilter(typeFilter, ret_ty[1])) {
+                    if (!typePassesFilter(typeFilter, ret_ty.ty)) {
                         continue;
                     }
                     lev = Math.min(lev, checkType(ret_ty, elem, parsedQuery.literalSearch));
@@ -1836,6 +1825,97 @@ function initSearch(rawSearchIndex) {
             filterCrates);
     }
 
+    /**
+     * Convert a list of RawFunctionType / ID to object-based FunctionType.
+     *
+     * Crates often have lots of functions in them, and it's common to have a large number of
+     * functions that operate on a small set of data types, so the search index compresses them
+     * by encoding function parameter and return types as indexes into an array of names.
+     *
+     * Even when a general-purpose compression algorithm is used, this is still a win. I checked.
+     * https://github.com/rust-lang/rust/pull/98475#issue-1284395985
+     *
+     * The format for individual function types is encoded in
+     * librustdoc/html/render/mod.rs: impl Serialize for RenderType
+     *
+     * @param {null|Array<RawFunctionType>} types
+     * @param {Array<{name: string, ty: number}>} lowercasePaths
+     *
+     * @return {Array<FunctionSearchType>}
+     */
+    function buildItemSearchTypeAll(types, lowercasePaths) {
+        const PATH_INDEX_DATA = 0;
+        const GENERICS_DATA = 1;
+        return types.map(type => {
+            let pathIndex, generics;
+            if (typeof type === "number") {
+                pathIndex = type;
+                generics = [];
+            } else {
+                pathIndex = type[PATH_INDEX_DATA];
+                generics = buildItemSearchTypeAll(type[GENERICS_DATA], lowercasePaths);
+            }
+            return {
+                // `0` is used as a sentinel because it's fewer bytes than `null`
+                name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name,
+                ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
+                generics: generics,
+            };
+        });
+    }
+
+    /**
+     * Convert from RawFunctionSearchType to FunctionSearchType.
+     *
+     * Crates often have lots of functions in them, and function signatures are sometimes complex,
+     * so rustdoc uses a pretty tight encoding for them. This function converts it to a simpler,
+     * object-based encoding so that the actual search code is more readable and easier to debug.
+     *
+     * The raw function search type format is generated using serde in
+     * librustdoc/html/render/mod.rs: impl Serialize for IndexItemFunctionType
+     *
+     * @param {RawFunctionSearchType} functionSearchType
+     * @param {Array<{name: string, ty: number}>} lowercasePaths
+     *
+     * @return {null|FunctionSearchType}
+     */
+    function buildFunctionSearchType(functionSearchType, lowercasePaths) {
+        const INPUTS_DATA = 0;
+        const OUTPUT_DATA = 1;
+        // `0` is used as a sentinel because it's fewer bytes than `null`
+        if (functionSearchType === 0) {
+            return null;
+        }
+        let inputs, output;
+        if (typeof functionSearchType[INPUTS_DATA] === "number") {
+            const pathIndex = functionSearchType[INPUTS_DATA];
+            inputs = [{
+                name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name,
+                ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
+                generics: [],
+            }];
+        } else {
+            inputs = buildItemSearchTypeAll(functionSearchType[INPUTS_DATA], lowercasePaths);
+        }
+        if (functionSearchType.length > 1) {
+            if (typeof functionSearchType[OUTPUT_DATA] === "number") {
+                const pathIndex = functionSearchType[OUTPUT_DATA];
+                output = [{
+                    name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name,
+                    ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
+                    generics: [],
+                }];
+            } else {
+                output = buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA], lowercasePaths);
+            }
+        } else {
+            output = [];
+        }
+        return {
+            inputs, output,
+        };
+    }
+
     function buildIndex(rawSearchIndex) {
         searchIndex = [];
         /**
@@ -1862,14 +1942,22 @@ function initSearch(rawSearchIndex) {
              * q[i] contains the full path of the item, or an empty string indicating
              * "same as q[i-1]".
              *
-             * i[i], f[i] are a mystery.
+             * i[i] contains an item's parent, usually a module. For compactness,
+             * it is a set of indexes into the `p` array.
+             *
+             * f[i] contains function signatures, or `0` if the item isn't a function.
+             * Functions are themselves encoded as arrays. The first item is a list of
+             * types representing the function's inputs, and the second list item is a list
+             * of types representing the function's output. Tuples are flattened.
+             * Types are also represented as arrays; the first item is an index into the `p`
+             * array, while the second is a list of types representing any generic parameters.
              *
              * `a` defines aliases with an Array of pairs: [name, offset], where `offset`
              * points into the n/t/d/q/i/f arrays.
              *
              * `doc` contains the description of the crate.
              *
-             * `p` is a mystery and isn't the same length as n/t/d/q/i/f.
+             * `p` is a list of path/type pairs. It is used for parents and function parameters.
              *
              * @type {{
              *   doc: string,
@@ -1879,7 +1967,7 @@ function initSearch(rawSearchIndex) {
              *   d: Array<string>,
              *   q: Array<string>,
              *   i: Array<Number>,
-             *   f: Array<Array<?>>,
+             *   f: Array<RawFunctionSearchType>,
              *   p: Array<Object>,
              * }}
              */
@@ -1923,9 +2011,14 @@ function initSearch(rawSearchIndex) {
             //             [Number] index to items]
             const aliases = crateCorpus.a;
 
+            // an array of [{name: String, ty: Number}]
+            const lowercasePaths = [];
+
             // convert `rawPaths` entries into object form
+            // generate normalizedPaths for function search mode
             let len = paths.length;
             for (i = 0; i < len; ++i) {
+                lowercasePaths.push({ty: paths[i][0], name: paths[i][1].toLowerCase()});
                 paths[i] = {ty: paths[i][0], name: paths[i][1]};
             }
 
@@ -1955,7 +2048,7 @@ function initSearch(rawSearchIndex) {
                     path: itemPaths[i] ? itemPaths[i] : lastPath,
                     desc: itemDescs[i],
                     parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
-                    type: itemFunctionSearchTypes[i],
+                    type: buildFunctionSearchType(itemFunctionSearchTypes[i], lowercasePaths),
                     id: id,
                     normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
                 };
diff --git a/src/librustdoc/html/static/js/source-script.js b/src/librustdoc/html/static/js/source-script.js
index 290c29d3141..acb1d8d7b5c 100644
--- a/src/librustdoc/html/static/js/source-script.js
+++ b/src/librustdoc/html/static/js/source-script.js
@@ -10,6 +10,7 @@
 (function() {
 
 const rootPath = document.getElementById("rustdoc-vars").attributes["data-root-path"].value;
+let oldScrollPosition = 0;
 
 function createDirEntry(elem, parent, fullPath, hasFoundFile) {
     const name = document.createElement("div");
@@ -65,10 +66,24 @@ function createDirEntry(elem, parent, fullPath, hasFoundFile) {
 function toggleSidebar() {
     const child = this.children[0];
     if (child.innerText === ">") {
+        if (window.innerWidth < 701) {
+            // This is to keep the scroll position on mobile.
+            oldScrollPosition = window.scrollY;
+            document.body.style.position = "fixed";
+            document.body.style.top = `-${oldScrollPosition}px`;
+        }
         addClass(document.documentElement, "source-sidebar-expanded");
         child.innerText = "<";
         updateLocalStorage("source-sidebar-show", "true");
     } else {
+        if (window.innerWidth < 701) {
+            // This is to keep the scroll position on mobile.
+            document.body.style.position = "";
+            document.body.style.top = "";
+            // The scroll position is lost when resetting the style, hence why we store it in
+            // `oldScroll`.
+            window.scrollTo(0, oldScrollPosition);
+        }
         removeClass(document.documentElement, "source-sidebar-expanded");
         child.innerText = ">";
         updateLocalStorage("source-sidebar-show", "false");