about summary refs log tree commit diff
path: root/src/librustdoc/html
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/html')
-rw-r--r--src/librustdoc/html/format.rs43
-rw-r--r--src/librustdoc/html/highlight.rs7
-rw-r--r--src/librustdoc/html/markdown.rs17
-rw-r--r--src/librustdoc/html/render/context.rs4
-rw-r--r--src/librustdoc/html/render/mod.rs111
-rw-r--r--src/librustdoc/html/render/print_item.rs33
-rw-r--r--src/librustdoc/html/render/search_index.rs215
-rw-r--r--src/librustdoc/html/render/search_index/encode.rs23
-rw-r--r--src/librustdoc/html/static/css/noscript.css1
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css48
-rw-r--r--src/librustdoc/html/static/images/clipboard.svg1
-rw-r--r--src/librustdoc/html/static/images/wheel.svg1
-rw-r--r--src/librustdoc/html/static/js/externs.js19
-rw-r--r--src/librustdoc/html/static/js/main.js20
-rw-r--r--src/librustdoc/html/static/js/search.js87
-rw-r--r--src/librustdoc/html/static/js/storage.js43
-rw-r--r--src/librustdoc/html/static_files.rs2
-rw-r--r--src/librustdoc/html/templates/page.html27
18 files changed, 470 insertions, 232 deletions
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index f82b89fdd5f..57949001774 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -13,7 +13,7 @@ use std::fmt::{self, Display, Write};
 use std::iter::{self, once};
 
 use rustc_ast as ast;
-use rustc_attr::{ConstStability, StabilityLevel};
+use rustc_attr::{ConstStability, StabilityLevel, StableSince};
 use rustc_data_structures::captures::Captures;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir as hir;
@@ -594,9 +594,9 @@ fn generate_item_def_id_path(
     root_path: Option<&str>,
     original_def_kind: DefKind,
 ) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
-    use crate::rustc_trait_selection::infer::TyCtxtInferExt;
-    use crate::rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
     use rustc_middle::traits::ObligationCause;
+    use rustc_trait_selection::infer::TyCtxtInferExt;
+    use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
 
     let tcx = cx.tcx();
     let crate_name = tcx.crate_name(def_id.krate);
@@ -1013,7 +1013,7 @@ fn fmt_type<'cx>(
         }
         clean::BareFunction(ref decl) => {
             print_higher_ranked_params_with_space(&decl.generic_params, cx).fmt(f)?;
-            decl.unsafety.print_with_space().fmt(f)?;
+            decl.safety.print_with_space().fmt(f)?;
             print_abi_with_space(decl.abi).fmt(f)?;
             if f.alternate() {
                 f.write_str("fn")?;
@@ -1303,7 +1303,7 @@ impl clean::Impl {
                 // Link should match `# Trait implementations`
 
                 print_higher_ranked_params_with_space(&bare_fn.generic_params, cx).fmt(f)?;
-                bare_fn.unsafety.print_with_space().fmt(f)?;
+                bare_fn.safety.print_with_space().fmt(f)?;
                 print_abi_with_space(bare_fn.abi).fmt(f)?;
                 let ellipsis = if bare_fn.decl.c_variadic { ", ..." } else { "" };
                 primitive_link_fragment(
@@ -1604,11 +1604,11 @@ pub(crate) trait PrintWithSpace {
     fn print_with_space(&self) -> &str;
 }
 
-impl PrintWithSpace for hir::Unsafety {
+impl PrintWithSpace for hir::Safety {
     fn print_with_space(&self) -> &str {
         match self {
-            hir::Unsafety::Unsafe => "unsafe ",
-            hir::Unsafety::Normal => "",
+            hir::Safety::Unsafe => "unsafe ",
+            hir::Safety::Safe => "",
         }
     }
 }
@@ -1633,17 +1633,24 @@ impl PrintWithSpace for hir::Mutability {
 
 pub(crate) fn print_constness_with_space(
     c: &hir::Constness,
-    s: Option<ConstStability>,
+    overall_stab: Option<StableSince>,
+    const_stab: Option<ConstStability>,
 ) -> &'static str {
-    match (c, s) {
-        // const stable or when feature(staged_api) is not set
-        (
-            hir::Constness::Const,
-            Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }),
-        )
-        | (hir::Constness::Const, None) => "const ",
-        // const unstable or not const
-        _ => "",
+    match c {
+        hir::Constness::Const => match (overall_stab, const_stab) {
+            // const stable...
+            (_, Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }))
+            // ...or when feature(staged_api) is not set...
+            | (_, None)
+            // ...or when const unstable, but overall unstable too
+            | (None, Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. })) => {
+                "const "
+            }
+            // const unstable (and overall stable)
+            (Some(_), Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. })) => "",
+        },
+        // not const
+        hir::Constness::NotConst => "",
     }
 }
 
diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index aa5998876d9..336d18a1df1 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -876,9 +876,10 @@ impl<'src> Classifier<'src> {
                 },
                 Some(c) => c,
             },
-            TokenKind::RawIdent | TokenKind::UnknownPrefix | TokenKind::InvalidIdent => {
-                Class::Ident(self.new_span(before, text))
-            }
+            TokenKind::RawIdent
+            | TokenKind::UnknownPrefix
+            | TokenKind::InvalidPrefix
+            | TokenKind::InvalidIdent => Class::Ident(self.new_span(before, text)),
             TokenKind::Lifetime { .. } => Class::Lifetime,
             TokenKind::Eof => panic!("Eof in advance"),
         };
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 64f0e096cd0..362f9021671 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -35,13 +35,13 @@ use rustc_resolve::rustdoc::may_be_doc_link;
 use rustc_span::edition::Edition;
 use rustc_span::{Span, Symbol};
 
-use once_cell::sync::Lazy;
 use std::borrow::Cow;
 use std::collections::VecDeque;
 use std::fmt::Write;
 use std::iter::Peekable;
 use std::ops::{ControlFlow, Range};
 use std::str::{self, CharIndices};
+use std::sync::OnceLock;
 
 use crate::clean::RenderedLink;
 use crate::doctest;
@@ -834,8 +834,9 @@ impl<'tcx> ExtraInfo<'tcx> {
                 crate::lint::INVALID_CODEBLOCK_ATTRIBUTES,
                 self.tcx.local_def_id_to_hir_id(def_id),
                 self.sp,
-                msg,
-                |_| {},
+                |lint| {
+                    lint.primary_message(msg);
+                },
             );
         }
     }
@@ -850,8 +851,10 @@ impl<'tcx> ExtraInfo<'tcx> {
                 crate::lint::INVALID_CODEBLOCK_ATTRIBUTES,
                 self.tcx.local_def_id_to_hir_id(def_id),
                 self.sp,
-                msg,
-                f,
+                |lint| {
+                    lint.primary_message(msg);
+                    f(lint);
+                },
             );
         }
     }
@@ -1994,7 +1997,7 @@ pub struct IdMap {
 }
 
 // The map is pre-initialized and cloned each time to avoid reinitializing it repeatedly.
-static DEFAULT_ID_MAP: Lazy<FxHashMap<Cow<'static, str>, usize>> = Lazy::new(|| init_id_map());
+static DEFAULT_ID_MAP: OnceLock<FxHashMap<Cow<'static, str>, usize>> = OnceLock::new();
 
 fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> {
     let mut map = FxHashMap::default();
@@ -2051,7 +2054,7 @@ fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> {
 
 impl IdMap {
     pub fn new() -> Self {
-        IdMap { map: DEFAULT_ID_MAP.clone() }
+        IdMap { map: DEFAULT_ID_MAP.get_or_init(init_id_map).clone() }
     }
 
     pub(crate) fn derive<S: AsRef<str> + ToString>(&mut self, candidate: S) -> String {
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 4cab2d64257..db1119eca1d 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -78,8 +78,10 @@ pub(crate) struct Context<'tcx> {
 }
 
 // `Context` is cloned a lot, so we don't want the size to grow unexpectedly.
-#[cfg(all(not(windows), target_arch = "x86_64", target_pointer_width = "64"))]
+#[cfg(all(not(windows), target_pointer_width = "64"))]
 rustc_data_structures::static_assert_size!(Context<'_>, 160);
+#[cfg(all(windows, target_pointer_width = "64"))]
+rustc_data_structures::static_assert_size!(Context<'_>, 168);
 
 /// Shared mutable state used in [`Context`] and elsewhere.
 pub(crate) struct SharedContext<'tcx> {
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index a949f795753..8ee4cc5c75e 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -52,6 +52,7 @@ use rustc_data_structures::captures::Captures;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_hir::def_id::{DefId, DefIdSet};
 use rustc_hir::Mutability;
+use rustc_middle::ty::print::PrintTraitRefExt;
 use rustc_middle::ty::{self, TyCtxt};
 use rustc_session::RustcVersion;
 use rustc_span::{
@@ -111,11 +112,13 @@ pub(crate) enum RenderMode {
 #[derive(Debug)]
 pub(crate) struct IndexItem {
     pub(crate) ty: ItemType,
+    pub(crate) defid: Option<DefId>,
     pub(crate) name: Symbol,
     pub(crate) path: String,
     pub(crate) desc: String,
     pub(crate) parent: Option<DefId>,
     pub(crate) parent_idx: Option<isize>,
+    pub(crate) exact_path: Option<String>,
     pub(crate) impl_id: Option<DefId>,
     pub(crate) search_type: Option<IndexItemFunctionType>,
     pub(crate) aliases: Box<[Symbol]>,
@@ -180,6 +183,7 @@ pub(crate) enum RenderTypeId {
     Primitive(clean::PrimitiveType),
     AssociatedType(Symbol),
     Index(isize),
+    Mut,
 }
 
 impl RenderTypeId {
@@ -924,13 +928,15 @@ fn assoc_method(
     // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
     // this condition.
     let constness = match render_mode {
-        RenderMode::Normal => {
-            print_constness_with_space(&header.constness, meth.const_stability(tcx))
-        }
+        RenderMode::Normal => print_constness_with_space(
+            &header.constness,
+            meth.stable_since(tcx),
+            meth.const_stability(tcx),
+        ),
         RenderMode::ForDeref { .. } => "",
     };
     let asyncness = header.asyncness.print_with_space();
-    let unsafety = header.unsafety.print_with_space();
+    let safety = header.safety.print_with_space();
     let abi = print_abi_with_space(header.abi).to_string();
     let href = assoc_href_attr(meth, link, cx);
 
@@ -941,7 +947,7 @@ fn assoc_method(
         + defaultness.len()
         + constness.len()
         + asyncness.len()
-        + unsafety.len()
+        + safety.len()
         + abi.len()
         + name.as_str().len()
         + generics_len;
@@ -960,14 +966,14 @@ fn assoc_method(
     w.reserve(header_len + "<a href=\"\" class=\"fn\">{".len() + "</a>".len());
     write!(
         w,
-        "{indent}{vis}{defaultness}{constness}{asyncness}{unsafety}{abi}fn \
+        "{indent}{vis}{defaultness}{constness}{asyncness}{safety}{abi}fn \
          <a{href} class=\"fn\">{name}</a>{generics}{decl}{notable_traits}{where_clause}",
         indent = indent_str,
         vis = vis,
         defaultness = defaultness,
         constness = constness,
         asyncness = asyncness,
-        unsafety = unsafety,
+        safety = safety,
         abi = abi,
         href = href,
         name = name,
@@ -994,48 +1000,41 @@ fn assoc_method(
 /// consequence of the above rules.
 fn render_stability_since_raw_with_extra(
     w: &mut Buffer,
-    ver: Option<StableSince>,
+    stable_version: Option<StableSince>,
     const_stability: Option<ConstStability>,
-    containing_ver: Option<StableSince>,
-    containing_const_ver: Option<StableSince>,
     extra_class: &str,
 ) -> bool {
-    let stable_version = if ver != containing_ver
-        && let Some(ver) = &ver
-    {
-        since_to_string(ver)
-    } else {
-        None
-    };
-
     let mut title = String::new();
     let mut stability = String::new();
 
-    if let Some(ver) = stable_version {
-        stability.push_str(ver.as_str());
-        title.push_str(&format!("Stable since Rust version {ver}"));
+    if let Some(version) = stable_version.and_then(|version| since_to_string(&version)) {
+        stability.push_str(&version);
+        title.push_str(&format!("Stable since Rust version {version}"));
     }
 
     let const_title_and_stability = match const_stability {
-        Some(ConstStability { level: StabilityLevel::Stable { since, .. }, .. })
-            if Some(since) != containing_const_ver =>
-        {
+        Some(ConstStability { level: StabilityLevel::Stable { since, .. }, .. }) => {
             since_to_string(&since)
                 .map(|since| (format!("const since {since}"), format!("const: {since}")))
         }
         Some(ConstStability { level: StabilityLevel::Unstable { issue, .. }, feature, .. }) => {
-            let unstable = if let Some(n) = issue {
-                format!(
-                    "<a \
+            if stable_version.is_none() {
+                // don't display const unstable if entirely unstable
+                None
+            } else {
+                let unstable = if let Some(n) = issue {
+                    format!(
+                        "<a \
                         href=\"https://github.com/rust-lang/rust/issues/{n}\" \
                         title=\"Tracking issue for {feature}\"\
                        >unstable</a>"
-                )
-            } else {
-                String::from("unstable")
-            };
+                    )
+                } else {
+                    String::from("unstable")
+                };
 
-            Some((String::from("const unstable"), format!("const: {unstable}")))
+                Some((String::from("const unstable"), format!("const: {unstable}")))
+            }
         }
         _ => None,
     };
@@ -1074,17 +1073,8 @@ fn render_stability_since_raw(
     w: &mut Buffer,
     ver: Option<StableSince>,
     const_stability: Option<ConstStability>,
-    containing_ver: Option<StableSince>,
-    containing_const_ver: Option<StableSince>,
 ) -> bool {
-    render_stability_since_raw_with_extra(
-        w,
-        ver,
-        const_stability,
-        containing_ver,
-        containing_const_ver,
-        "",
-    )
+    render_stability_since_raw_with_extra(w, ver, const_stability, "")
 }
 
 fn render_assoc_item(
@@ -1441,6 +1431,10 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> O
     if let Some(impls) = cx.cache().impls.get(&did) {
         for i in impls {
             let impl_ = i.inner_impl();
+            if impl_.polarity != ty::ImplPolarity::Positive {
+                continue;
+            }
+
             if !ty.is_doc_subtype_of(&impl_.for_, cx.cache()) {
                 // Two different types might have the same did,
                 // without actually being the same.
@@ -1476,6 +1470,10 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
 
     for i in impls {
         let impl_ = i.inner_impl();
+        if impl_.polarity != ty::ImplPolarity::Positive {
+            continue;
+        }
+
         if !ty.is_doc_subtype_of(&impl_.for_, cx.cache()) {
             // Two different types might have the same did,
             // without actually being the same.
@@ -1583,7 +1581,6 @@ fn render_impl(
         cx: &mut Context<'_>,
         item: &clean::Item,
         parent: &clean::Item,
-        containing_item: &clean::Item,
         link: AssocItemLink<'_>,
         render_mode: RenderMode,
         is_default_item: bool,
@@ -1679,7 +1676,7 @@ fn render_impl(
                         })
                         .map(|item| format!("{}.{name}", item.type_()));
                     write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">");
-                    render_rightside(w, cx, item, containing_item, render_mode);
+                    render_rightside(w, cx, item, render_mode);
                     if trait_.is_some() {
                         // Anchors are only used on trait impls.
                         write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>");
@@ -1701,7 +1698,7 @@ fn render_impl(
                 let source_id = format!("{item_type}.{name}");
                 let id = cx.derive_id(&source_id);
                 write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">");
-                render_rightside(w, cx, item, containing_item, render_mode);
+                render_rightside(w, cx, item, render_mode);
                 if trait_.is_some() {
                     // Anchors are only used on trait impls.
                     write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>");
@@ -1787,7 +1784,6 @@ fn render_impl(
             cx,
             trait_item,
             if trait_.is_some() { &i.impl_item } else { parent },
-            parent,
             link,
             render_mode,
             false,
@@ -1803,7 +1799,6 @@ fn render_impl(
         t: &clean::Trait,
         i: &clean::Impl,
         parent: &clean::Item,
-        containing_item: &clean::Item,
         render_mode: RenderMode,
         rendering_params: ImplRenderingParameters,
     ) {
@@ -1831,7 +1826,6 @@ fn render_impl(
                 cx,
                 trait_item,
                 parent,
-                containing_item,
                 assoc_link,
                 render_mode,
                 true,
@@ -1854,7 +1848,6 @@ fn render_impl(
                 t,
                 i.inner_impl(),
                 &i.impl_item,
-                parent,
                 render_mode,
                 rendering_params,
             );
@@ -1876,7 +1869,6 @@ fn render_impl(
             cx,
             i,
             parent,
-            parent,
             rendering_params.show_def_docs,
             use_absolute,
             aliases,
@@ -1924,20 +1916,14 @@ fn render_impl(
 
 // Render the items that appear on the right side of methods, impls, and
 // associated types. For example "1.0.0 (const: 1.39.0) · source".
-fn render_rightside(
-    w: &mut Buffer,
-    cx: &Context<'_>,
-    item: &clean::Item,
-    containing_item: &clean::Item,
-    render_mode: RenderMode,
-) {
+fn render_rightside(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, render_mode: RenderMode) {
     let tcx = cx.tcx();
 
     // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
     // this condition.
-    let (const_stability, const_stable_since) = match render_mode {
-        RenderMode::Normal => (item.const_stability(tcx), containing_item.const_stable_since(tcx)),
-        RenderMode::ForDeref { .. } => (None, None),
+    let const_stability = match render_mode {
+        RenderMode::Normal => item.const_stability(tcx),
+        RenderMode::ForDeref { .. } => None,
     };
     let src_href = cx.src_href(item);
     let has_src_ref = src_href.is_some();
@@ -1947,8 +1933,6 @@ fn render_rightside(
         &mut rightside,
         item.stable_since(tcx),
         const_stability,
-        containing_item.stable_since(tcx),
-        const_stable_since,
         if has_src_ref { "" } else { " rightside" },
     );
     if let Some(link) = src_href {
@@ -1970,7 +1954,6 @@ pub(crate) fn render_impl_summary(
     cx: &mut Context<'_>,
     i: &Impl,
     parent: &clean::Item,
-    containing_item: &clean::Item,
     show_def_docs: bool,
     use_absolute: Option<bool>,
     // This argument is used to reference same type with different paths to avoid duplication
@@ -1985,7 +1968,7 @@ pub(crate) fn render_impl_summary(
         format!(" data-aliases=\"{}\"", aliases.join(","))
     };
     write!(w, "<section id=\"{id}\" class=\"impl\"{aliases}>");
-    render_rightside(w, cx, &i.impl_item, containing_item, RenderMode::Normal);
+    render_rightside(w, cx, &i.impl_item, RenderMode::Normal);
     write!(
         w,
         "<a href=\"#{id}\" class=\"anchor\">§</a>\
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 168db5c0948..c5b88c7a951 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -214,8 +214,6 @@ pub(super) fn print_item(cx: &mut Context<'_>, item: &clean::Item, buf: &mut Buf
         &mut stability_since_raw,
         item.stable_since(cx.tcx()),
         item.const_stability(cx.tcx()),
-        None,
-        None,
     );
     let stability_since_raw: String = stability_since_raw.into_inner();
 
@@ -494,7 +492,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
 
                 let unsafety_flag = match *myitem.kind {
                     clean::FunctionItem(_) | clean::ForeignFunctionItem(_)
-                        if myitem.fn_header(tcx).unwrap().unsafety == hir::Unsafety::Unsafe =>
+                        if myitem.fn_header(tcx).unwrap().safety == hir::Safety::Unsafe =>
                     {
                         "<sup title=\"unsafe function\">⚠</sup>"
                     }
@@ -617,8 +615,19 @@ fn extra_info_tags<'a, 'tcx: 'a>(
 fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &clean::Function) {
     let tcx = cx.tcx();
     let header = it.fn_header(tcx).expect("printing a function which isn't a function");
-    let constness = print_constness_with_space(&header.constness, it.const_stability(tcx));
-    let unsafety = header.unsafety.print_with_space();
+    debug!(
+        "item_function/const: {:?} {:?} {:?} {:?}",
+        it.name,
+        &header.constness,
+        it.stable_since(tcx),
+        it.const_stability(tcx),
+    );
+    let constness = print_constness_with_space(
+        &header.constness,
+        it.stable_since(tcx),
+        it.const_stability(tcx),
+    );
+    let safety = header.safety.print_with_space();
     let abi = print_abi_with_space(header.abi).to_string();
     let asyncness = header.asyncness.print_with_space();
     let visibility = visibility_print_with_space(it, cx).to_string();
@@ -629,7 +638,7 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
         + visibility.len()
         + constness.len()
         + asyncness.len()
-        + unsafety.len()
+        + safety.len()
         + abi.len()
         + name.as_str().len()
         + generics_len;
@@ -640,13 +649,13 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
         w.reserve(header_len);
         write!(
             w,
-            "{attrs}{vis}{constness}{asyncness}{unsafety}{abi}fn \
+            "{attrs}{vis}{constness}{asyncness}{safety}{abi}fn \
                 {name}{generics}{decl}{notable_traits}{where_clause}",
             attrs = render_attributes_in_pre(it, "", cx),
             vis = visibility,
             constness = constness,
             asyncness = asyncness,
-            unsafety = unsafety,
+            safety = safety,
             abi = abi,
             name = name,
             generics = f.generics.print(cx),
@@ -676,10 +685,10 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
     wrap_item(w, |mut w| {
         write!(
             w,
-            "{attrs}{vis}{unsafety}{is_auto}trait {name}{generics}{bounds}",
+            "{attrs}{vis}{safety}{is_auto}trait {name}{generics}{bounds}",
             attrs = render_attributes_in_pre(it, "", cx),
             vis = visibility_print_with_space(it, cx),
-            unsafety = t.unsafety(tcx).print_with_space(),
+            safety = t.safety(tcx).print_with_space(),
             is_auto = if t.is_auto(tcx) { "auto " } else { "" },
             name = it.name.unwrap(),
             generics = t.generics.print(cx),
@@ -825,7 +834,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
             write!(w, "<details class=\"toggle{method_toggle_class}\" open><summary>");
         }
         write!(w, "<section id=\"{id}\" class=\"method\">");
-        render_rightside(w, cx, m, t, RenderMode::Normal);
+        render_rightside(w, cx, m, RenderMode::Normal);
         write!(w, "<h4 class=\"code-header\">");
         render_assoc_item(
             w,
@@ -1686,8 +1695,6 @@ fn item_variants(
             w,
             variant.stable_since(tcx),
             variant.const_stability(tcx),
-            it.stable_since(tcx),
-            it.const_stable_since(tcx),
             " rightside",
         );
         w.write_str("<h3 class=\"code-header\">");
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index 7083999de68..e635c1e611d 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -7,7 +7,7 @@ use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
 use rustc_middle::ty::TyCtxt;
 use rustc_span::def_id::DefId;
 use rustc_span::sym;
-use rustc_span::symbol::Symbol;
+use rustc_span::symbol::{kw, Symbol};
 use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
 use thin_vec::ThinVec;
 
@@ -59,10 +59,13 @@ pub(crate) fn build_index<'tcx>(
     cache: &mut Cache,
     tcx: TyCtxt<'tcx>,
 ) -> SerializedSearchIndex {
+    // Maps from ID to position in the `crate_paths` array.
     let mut itemid_to_pathid = FxHashMap::default();
     let mut primitives = FxHashMap::default();
     let mut associated_types = FxHashMap::default();
-    let mut crate_paths = vec![];
+
+    // item type, display path, re-exported internal path
+    let mut crate_paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)> = vec![];
 
     // Attach all orphan items to the type's definition if the type
     // has since been learned.
@@ -72,11 +75,13 @@ pub(crate) fn build_index<'tcx>(
             let desc = short_markdown_summary(&item.doc_value(), &item.link_names(cache));
             cache.search_index.push(IndexItem {
                 ty: item.type_(),
+                defid: item.item_id.as_def_id(),
                 name: item.name.unwrap(),
                 path: join_with_double_colon(&fqp[..fqp.len() - 1]),
                 desc,
                 parent: Some(parent),
                 parent_idx: None,
+                exact_path: None,
                 impl_id,
                 search_type: get_function_type_for_search(
                     item,
@@ -126,9 +131,10 @@ pub(crate) fn build_index<'tcx>(
             map: &mut FxHashMap<F, isize>,
             itemid: F,
             lastpathid: &mut isize,
-            crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
+            crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
             item_type: ItemType,
             path: &[Symbol],
+            exact_path: Option<&[Symbol]>,
         ) -> RenderTypeId {
             match map.entry(itemid) {
                 Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()),
@@ -136,7 +142,11 @@ pub(crate) fn build_index<'tcx>(
                     let pathid = *lastpathid;
                     entry.insert(pathid);
                     *lastpathid += 1;
-                    crate_paths.push((item_type, path.to_vec()));
+                    crate_paths.push((
+                        item_type,
+                        path.to_vec(),
+                        exact_path.map(|path| path.to_vec()),
+                    ));
                     RenderTypeId::Index(pathid)
                 }
             }
@@ -149,14 +159,33 @@ pub(crate) fn build_index<'tcx>(
             primitives: &mut FxHashMap<Symbol, isize>,
             associated_types: &mut FxHashMap<Symbol, isize>,
             lastpathid: &mut isize,
-            crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
+            crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
         ) -> Option<RenderTypeId> {
-            let Cache { ref paths, ref external_paths, .. } = *cache;
+            let Cache { ref paths, ref external_paths, ref exact_paths, .. } = *cache;
             match id {
+                RenderTypeId::Mut => Some(insert_into_map(
+                    primitives,
+                    kw::Mut,
+                    lastpathid,
+                    crate_paths,
+                    ItemType::Keyword,
+                    &[kw::Mut],
+                    None,
+                )),
                 RenderTypeId::DefId(defid) => {
                     if let Some(&(ref fqp, item_type)) =
                         paths.get(&defid).or_else(|| external_paths.get(&defid))
                     {
+                        let exact_fqp = exact_paths
+                            .get(&defid)
+                            .or_else(|| external_paths.get(&defid).map(|&(ref fqp, _)| fqp))
+                            // Re-exports only count if the name is exactly the same.
+                            // This is a size optimization, since it means we only need
+                            // to store the name once (and the path is re-used for everything
+                            // exported from this same module). It's also likely to Do
+                            // What I Mean, since if a re-export changes the name, it might
+                            // also be a change in semantic meaning.
+                            .filter(|fqp| fqp.last() == fqp.last());
                         Some(insert_into_map(
                             itemid_to_pathid,
                             ItemId::DefId(defid),
@@ -164,6 +193,7 @@ pub(crate) fn build_index<'tcx>(
                             crate_paths,
                             item_type,
                             fqp,
+                            exact_fqp.map(|x| &x[..]).filter(|exact_fqp| exact_fqp != fqp),
                         ))
                     } else {
                         None
@@ -178,6 +208,7 @@ pub(crate) fn build_index<'tcx>(
                         crate_paths,
                         ItemType::Primitive,
                         &[sym],
+                        None,
                     ))
                 }
                 RenderTypeId::Index(_) => Some(id),
@@ -188,6 +219,7 @@ pub(crate) fn build_index<'tcx>(
                     crate_paths,
                     ItemType::AssocType,
                     &[sym],
+                    None,
                 )),
             }
         }
@@ -199,7 +231,7 @@ pub(crate) fn build_index<'tcx>(
             primitives: &mut FxHashMap<Symbol, isize>,
             associated_types: &mut FxHashMap<Symbol, isize>,
             lastpathid: &mut isize,
-            crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
+            crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
         ) {
             if let Some(generics) = &mut ty.generics {
                 for item in generics {
@@ -296,7 +328,7 @@ pub(crate) fn build_index<'tcx>(
         }
     }
 
-    let Cache { ref paths, .. } = *cache;
+    let Cache { ref paths, ref exact_paths, ref external_paths, .. } = *cache;
 
     // Then, on parent modules
     let crate_items: Vec<&IndexItem> = search_index
@@ -311,7 +343,13 @@ pub(crate) fn build_index<'tcx>(
                         lastpathid += 1;
 
                         if let Some(&(ref fqp, short)) = paths.get(&defid) {
-                            crate_paths.push((short, fqp.clone()));
+                            let exact_fqp = exact_paths
+                                .get(&defid)
+                                .or_else(|| external_paths.get(&defid).map(|&(ref fqp, _)| fqp))
+                                .filter(|exact_fqp| {
+                                    exact_fqp.last() == Some(&item.name) && *exact_fqp != fqp
+                                });
+                            crate_paths.push((short, fqp.clone(), exact_fqp.cloned()));
                             Some(pathid)
                         } else {
                             None
@@ -319,6 +357,42 @@ pub(crate) fn build_index<'tcx>(
                     }
                 });
 
+            if let Some(defid) = item.defid
+                && item.parent_idx.is_none()
+            {
+                // If this is a re-export, retain the original path.
+                // Associated items don't use this.
+                // Their parent carries the exact fqp instead.
+                let exact_fqp = exact_paths
+                    .get(&defid)
+                    .or_else(|| external_paths.get(&defid).map(|&(ref fqp, _)| fqp));
+                item.exact_path = exact_fqp.and_then(|fqp| {
+                    // Re-exports only count if the name is exactly the same.
+                    // This is a size optimization, since it means we only need
+                    // to store the name once (and the path is re-used for everything
+                    // exported from this same module). It's also likely to Do
+                    // What I Mean, since if a re-export changes the name, it might
+                    // also be a change in semantic meaning.
+                    if fqp.last() != Some(&item.name) {
+                        return None;
+                    }
+                    let path =
+                        if item.ty == ItemType::Macro && tcx.has_attr(defid, sym::macro_export) {
+                            // `#[macro_export]` always exports to the crate root.
+                            tcx.crate_name(defid.krate).to_string()
+                        } else {
+                            if fqp.len() < 2 {
+                                return None;
+                            }
+                            join_with_double_colon(&fqp[..fqp.len() - 1])
+                        };
+                    if path == item.path {
+                        return None;
+                    }
+                    Some(path)
+                });
+            }
+
             // Omit the parent path if it is same to that of the prior item.
             if lastpath == &item.path {
                 item.path.clear();
@@ -356,7 +430,7 @@ pub(crate) fn build_index<'tcx>(
 
     struct CrateData<'a> {
         items: Vec<&'a IndexItem>,
-        paths: Vec<(ItemType, Vec<Symbol>)>,
+        paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
         // The String is alias name and the vec is the list of the elements with this alias.
         //
         // To be noted: the `usize` elements are indexes to `items`.
@@ -374,6 +448,7 @@ pub(crate) fn build_index<'tcx>(
         ty: ItemType,
         name: Symbol,
         path: Option<usize>,
+        exact_path: Option<usize>,
     }
 
     impl Serialize for Paths {
@@ -387,6 +462,10 @@ pub(crate) fn build_index<'tcx>(
             if let Some(ref path) = self.path {
                 seq.serialize_element(path)?;
             }
+            if let Some(ref path) = self.exact_path {
+                assert!(self.path.is_some());
+                seq.serialize_element(path)?;
+            }
             seq.end()
         }
     }
@@ -409,14 +488,39 @@ pub(crate) fn build_index<'tcx>(
                 mod_paths.insert(&item.path, index);
             }
             let mut paths = Vec::with_capacity(self.paths.len());
-            for (ty, path) in &self.paths {
+            for (ty, path, exact) in &self.paths {
                 if path.len() < 2 {
-                    paths.push(Paths { ty: *ty, name: path[0], path: None });
+                    paths.push(Paths { ty: *ty, name: path[0], path: None, exact_path: None });
                     continue;
                 }
                 let full_path = join_with_double_colon(&path[..path.len() - 1]);
+                let full_exact_path = exact
+                    .as_ref()
+                    .filter(|exact| exact.last() == path.last() && exact.len() >= 2)
+                    .map(|exact| join_with_double_colon(&exact[..exact.len() - 1]));
+                let exact_path = extra_paths.len() + self.items.len();
+                let exact_path = full_exact_path.as_ref().map(|full_exact_path| match extra_paths
+                    .entry(full_exact_path.clone())
+                {
+                    Entry::Occupied(entry) => *entry.get(),
+                    Entry::Vacant(entry) => {
+                        if let Some(index) = mod_paths.get(&full_exact_path) {
+                            return *index;
+                        }
+                        entry.insert(exact_path);
+                        if !revert_extra_paths.contains_key(&exact_path) {
+                            revert_extra_paths.insert(exact_path, full_exact_path.clone());
+                        }
+                        exact_path
+                    }
+                });
                 if let Some(index) = mod_paths.get(&full_path) {
-                    paths.push(Paths { ty: *ty, name: *path.last().unwrap(), path: Some(*index) });
+                    paths.push(Paths {
+                        ty: *ty,
+                        name: *path.last().unwrap(),
+                        path: Some(*index),
+                        exact_path,
+                    });
                     continue;
                 }
                 // It means it comes from an external crate so the item and its path will be
@@ -424,28 +528,54 @@ pub(crate) fn build_index<'tcx>(
                 //
                 // `index` is put after the last `mod_paths`
                 let index = extra_paths.len() + self.items.len();
-                if !revert_extra_paths.contains_key(&index) {
-                    revert_extra_paths.insert(index, full_path.clone());
-                }
-                match extra_paths.entry(full_path) {
+                match extra_paths.entry(full_path.clone()) {
                     Entry::Occupied(entry) => {
                         paths.push(Paths {
                             ty: *ty,
                             name: *path.last().unwrap(),
                             path: Some(*entry.get()),
+                            exact_path,
                         });
                     }
                     Entry::Vacant(entry) => {
                         entry.insert(index);
+                        if !revert_extra_paths.contains_key(&index) {
+                            revert_extra_paths.insert(index, full_path);
+                        }
                         paths.push(Paths {
                             ty: *ty,
                             name: *path.last().unwrap(),
                             path: Some(index),
+                            exact_path,
                         });
                     }
                 }
             }
 
+            // Direct exports use adjacent arrays for the current crate's items,
+            // but re-exported exact paths don't.
+            let mut re_exports = Vec::new();
+            for (item_index, item) in self.items.iter().enumerate() {
+                if let Some(exact_path) = item.exact_path.as_ref() {
+                    if let Some(path_index) = mod_paths.get(&exact_path) {
+                        re_exports.push((item_index, *path_index));
+                    } else {
+                        let path_index = extra_paths.len() + self.items.len();
+                        let path_index = match extra_paths.entry(exact_path.clone()) {
+                            Entry::Occupied(entry) => *entry.get(),
+                            Entry::Vacant(entry) => {
+                                entry.insert(path_index);
+                                if !revert_extra_paths.contains_key(&path_index) {
+                                    revert_extra_paths.insert(path_index, exact_path.clone());
+                                }
+                                path_index
+                            }
+                        };
+                        re_exports.push((item_index, path_index));
+                    }
+                }
+            }
+
             let mut names = Vec::with_capacity(self.items.len());
             let mut types = String::with_capacity(self.items.len());
             let mut full_paths = Vec::with_capacity(self.items.len());
@@ -501,6 +631,7 @@ pub(crate) fn build_index<'tcx>(
             crate_data.serialize_field("f", &functions)?;
             crate_data.serialize_field("D", &self.desc_index)?;
             crate_data.serialize_field("p", &paths)?;
+            crate_data.serialize_field("r", &re_exports)?;
             crate_data.serialize_field("b", &self.associated_item_disambiguators)?;
             crate_data.serialize_field("c", &bitmap_to_string(&deprecated))?;
             crate_data.serialize_field("e", &bitmap_to_string(&self.empty_desc))?;
@@ -643,9 +774,8 @@ fn get_index_type_id(
             bounds.get(0).map(|b| RenderTypeId::DefId(b.trait_.def_id()))
         }
         clean::Primitive(p) => Some(RenderTypeId::Primitive(p)),
-        clean::BorrowedRef { ref type_, .. } | clean::RawPointer(_, ref type_) => {
-            get_index_type_id(type_, rgen)
-        }
+        clean::BorrowedRef { .. } => Some(RenderTypeId::Primitive(clean::PrimitiveType::Reference)),
+        clean::RawPointer(_, ref type_) => get_index_type_id(type_, rgen),
         // The type parameters are converted to generics in `simplify_fn_type`
         clean::Slice(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Slice)),
         clean::Array(_, _) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Array)),
@@ -711,28 +841,14 @@ fn simplify_fn_type<'tcx, 'a>(
     }
 
     // First, check if it's "Self".
-    let mut is_self = false;
-    let mut arg = if let Some(self_) = self_ {
-        match &*arg {
-            Type::BorrowedRef { type_, .. } if type_.is_self_type() => {
-                is_self = true;
-                self_
-            }
-            type_ if type_.is_self_type() => {
-                is_self = true;
-                self_
-            }
-            arg => arg,
-        }
+    let (is_self, arg) = if let Some(self_) = self_
+        && arg.is_self_type()
+    {
+        (true, self_)
     } else {
-        arg
+        (false, arg)
     };
 
-    // strip references from the argument type
-    while let Type::BorrowedRef { type_, .. } = &*arg {
-        arg = &*type_;
-    }
-
     // If this argument is a type parameter and not a trait bound or a type, we need to look
     // for its bounds.
     if let Type::Generic(arg_s) = *arg {
@@ -905,6 +1021,27 @@ fn simplify_fn_type<'tcx, 'a>(
             bindings: Some(ty_bindings),
             generics: Some(ty_generics),
         });
+    } else if let Type::BorrowedRef { lifetime: _, mutability, ref type_ } = *arg {
+        let mut ty_generics = Vec::new();
+        if mutability.is_mut() {
+            ty_generics.push(RenderType {
+                id: Some(RenderTypeId::Mut),
+                generics: None,
+                bindings: None,
+            });
+        }
+        simplify_fn_type(
+            self_,
+            generics,
+            &type_,
+            tcx,
+            recurse + 1,
+            &mut ty_generics,
+            rgen,
+            is_return,
+            cache,
+        );
+        res.push(get_index_type(arg, ty_generics, rgen));
     } else {
         // This is not a type parameter. So for example if we have `T, U: Option<T>`, and we're
         // looking at `Option`, we enter this "else" condition, otherwise if it's `T`, we don't.
diff --git a/src/librustdoc/html/render/search_index/encode.rs b/src/librustdoc/html/render/search_index/encode.rs
index 54407c614c4..8d715814faa 100644
--- a/src/librustdoc/html/render/search_index/encode.rs
+++ b/src/librustdoc/html/render/search_index/encode.rs
@@ -166,13 +166,12 @@ pub(crate) fn write_bitmap_to_bytes(
         containers.push(container);
     }
     // https://github.com/RoaringBitmap/RoaringFormatSpec
-    use byteorder::{WriteBytesExt, LE};
     const SERIAL_COOKIE_NO_RUNCONTAINER: u32 = 12346;
     const SERIAL_COOKIE: u32 = 12347;
     const NO_OFFSET_THRESHOLD: u32 = 4;
     let size: u32 = containers.len().try_into().unwrap();
     let start_offset = if has_run {
-        out.write_u32::<LE>(SERIAL_COOKIE | ((size - 1) << 16))?;
+        out.write_all(&u32::to_le_bytes(SERIAL_COOKIE | ((size - 1) << 16)))?;
         for set in containers.chunks(8) {
             let mut b = 0;
             for (i, container) in set.iter().enumerate() {
@@ -180,7 +179,7 @@ pub(crate) fn write_bitmap_to_bytes(
                     b |= 1 << i;
                 }
             }
-            out.write_u8(b)?;
+            out.write_all(&[b])?;
         }
         if size < NO_OFFSET_THRESHOLD {
             4 + 4 * size + ((size + 7) / 8)
@@ -188,21 +187,21 @@ pub(crate) fn write_bitmap_to_bytes(
             4 + 8 * size + ((size + 7) / 8)
         }
     } else {
-        out.write_u32::<LE>(SERIAL_COOKIE_NO_RUNCONTAINER)?;
-        out.write_u32::<LE>(containers.len().try_into().unwrap())?;
+        out.write_all(&u32::to_le_bytes(SERIAL_COOKIE_NO_RUNCONTAINER))?;
+        out.write_all(&u32::to_le_bytes(containers.len().try_into().unwrap()))?;
         4 + 4 + 4 * size + 4 * size
     };
     for (&key, container) in keys.iter().zip(&containers) {
         // descriptive header
         let key: u32 = key.into();
         let count: u32 = container.popcount() - 1;
-        out.write_u32::<LE>((count << 16) | key)?;
+        out.write_all(&u32::to_le_bytes((count << 16) | key))?;
     }
     if !has_run || size >= NO_OFFSET_THRESHOLD {
         // offset header
         let mut starting_offset = start_offset;
         for container in &containers {
-            out.write_u32::<LE>(starting_offset)?;
+            out.write_all(&u32::to_le_bytes(starting_offset))?;
             starting_offset += match container {
                 Container::Bits(_) => 8192u32,
                 Container::Array(array) => u32::try_from(array.len()).unwrap() * 2,
@@ -214,19 +213,19 @@ pub(crate) fn write_bitmap_to_bytes(
         match container {
             Container::Bits(bits) => {
                 for chunk in bits.iter() {
-                    out.write_u64::<LE>(*chunk)?;
+                    out.write_all(&u64::to_le_bytes(*chunk))?;
                 }
             }
             Container::Array(array) => {
                 for value in array.iter() {
-                    out.write_u16::<LE>(*value)?;
+                    out.write_all(&u16::to_le_bytes(*value))?;
                 }
             }
             Container::Run(runs) => {
-                out.write_u16::<LE>((runs.len()).try_into().unwrap())?;
+                out.write_all(&u16::to_le_bytes(runs.len().try_into().unwrap()))?;
                 for (start, lenm1) in runs.iter().copied() {
-                    out.write_u16::<LE>(start)?;
-                    out.write_u16::<LE>(lenm1)?;
+                    out.write_all(&u16::to_le_bytes(start))?;
+                    out.write_all(&u16::to_le_bytes(lenm1))?;
                 }
             }
         }
diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css
index ccb97d7df4c..6e10cf21537 100644
--- a/src/librustdoc/html/static/css/noscript.css
+++ b/src/librustdoc/html/static/css/noscript.css
@@ -85,6 +85,7 @@ nav.sub {
 	--search-tab-button-not-selected-background: #e6e6e6;
 	--search-tab-button-selected-border-top-color: #0089ff;
 	--search-tab-button-selected-background: #fff;
+	--settings-menu-filter: none;
 	--stab-background-color: #fff5d6;
 	--stab-code-color: #000;
 	--code-highlight-kw-color: #8959a8;
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index e9c687b42fa..4c0ba75d261 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -1133,6 +1133,7 @@ so that we can apply CSS-filters to change the arrow color in themes */
 .setting-check input:checked {
 	background-color: var(--settings-input-color);
 	border-width: 1px;
+	/* cross-mark image in the settings checkboxes */
 	content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">\
 		<path d="M7,25L17,32L33,12" fill="none" stroke="black" stroke-width="5"/>\
 		<path d="M7,23L17,30L33,10" fill="none" stroke="white" stroke-width="5"/></svg>');
@@ -1608,12 +1609,27 @@ a.tooltip:hover::after {
 	font-size: 0;
 }
 #settings-menu > a:before {
-	content: url('wheel-63255fc4502dca9a.svg');
+	/* Wheel <https://www.svgrepo.com/svg/384069/settings-cog-gear> */
+	content: url('data:image/svg+xml,<svg width="22" height="22" viewBox="0 0 12 12" \
+	enable-background="new 0 0 12 12" xmlns="http://www.w3.org/2000/svg">\
+	<path d="M10.25,6c0-0.1243286-0.0261841-0.241333-0.0366211-0.362915l1.6077881-1.5545654l\
+	-1.25-2.1650391  c0,0-1.2674561,0.3625488-2.1323853,0.6099854c-0.2034912-0.1431885-0.421875\
+	-0.2639771-0.6494751-0.3701782L7.25,0h-2.5 c0,0-0.3214111,1.2857666-0.5393066,2.1572876\
+	C3.9830933,2.2634888,3.7647095,2.3842773,3.5612183,2.5274658L1.428833,1.9174805 \
+	l-1.25,2.1650391c0,0,0.9641113,0.9321899,1.6077881,1.5545654C1.7761841,5.758667,\
+	1.75,5.8756714,1.75,6  s0.0261841,0.241333,0.0366211,0.362915L0.178833,7.9174805l1.25,\
+	2.1650391l2.1323853-0.6099854  c0.2034912,0.1432495,0.421875,0.2639771,0.6494751,0.3701782\
+	L4.75,12h2.5l0.5393066-2.1572876  c0.2276001-0.1062012,0.4459839-0.2269287,0.6494751\
+	-0.3701782l2.1323853,0.6099854l1.25-2.1650391L10.2133789,6.362915  C10.2238159,6.241333,\
+	10.25,6.1243286,10.25,6z M6,7.5C5.1715698,7.5,4.5,6.8284302,4.5,6S5.1715698,4.5,6,4.5S7.5\
+	,5.1715698,7.5,6  S6.8284302,7.5,6,7.5z" fill="black"/></svg>');
 	width: 22px;
 	height: 22px;
+	filter: var(--settings-menu-filter);
 }
 
 #sidebar-button > a:before {
+	/* sidebar resizer image */
 	content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" \
 		fill="none" stroke="black">\
 		<rect x="1" y="1" width="20" height="20" ry="1.5" stroke-width="1.5"/>\
@@ -1627,24 +1643,37 @@ a.tooltip:hover::after {
 	color: var(--copy-path-button-color);
 	background: var(--main-background-color);
 	height: 34px;
+	width: 33px;
 	margin-left: 10px;
 	padding: 0;
 	padding-left: 2px;
 	border: 0;
-	width: 33px;
-	line-height: 0;
 	font-size: 0;
 }
-
-#copy-path:before {
+#copy-path::before {
 	filter: var(--copy-path-img-filter);
-	content: url('clipboard-24048e6d87f63d07.svg');
+	/* clipboard <https://github.com/rust-lang/crates.io/commits/main/public/assets/copy.svg> */
+	content: url('data:image/svg+xml,<svg width="19" height="18" viewBox="0 0 24 25" \
+xmlns="http://www.w3.org/2000/svg" aria-label="Copy to clipboard">\
+<path d="M18 20h2v3c0 1-1 2-2 2H2c-.998 0-2-1-2-2V5c0-.911.755-1.667 1.667-1.667h5A3.323 3.323 0 \
+0110 0a3.323 3.323 0 013.333 3.333h5C19.245 3.333 20 4.09 20 5v8.333h-2V9H2v14h16v-3zM3 \
+7h14c0-.911-.793-1.667-1.75-1.667H13.5c-.957 0-1.75-.755-1.75-1.666C11.75 2.755 10.957 2 10 \
+2s-1.75.755-1.75 1.667c0 .911-.793 1.666-1.75 1.666H4.75C3.793 5.333 3 6.09 3 7z"/>\
+<path d="M4 19h6v2H4zM12 11H4v2h8zM4 17h4v-2H4zM15 15v-3l-4.5 4.5L15 21v-3l8.027-.032L23 15z"/>\
+</svg>');
 	width: 19px;
 	height: 18px;
 }
-#copy-path:hover:before {
+#copy-path:hover::before {
 	filter: var(--copy-path-img-hover-filter);
 }
+#copy-path.clicked::before {
+	/* Checkmark <https://www.svgrepo.com/svg/335033/checkmark> */
+	content: url('data:image/svg+xml,<svg viewBox="-1 -1 23 23" xmlns="http://www.w3.org/2000/svg" \
+		fill="black" height="18px">\
+		<g><path d="M9 19.414l-6.707-6.707 1.414-1.414L9 16.586 20.293 5.293l1.414 1.414"></path>\
+		</g></svg>');
+}
 
 @keyframes rotating {
 	from {
@@ -1834,6 +1863,7 @@ However, it's not needed with smaller screen width because the doc/code block is
 /* sidebar button opens modal
 	use hamburger button */
 .src #sidebar-button > a:before, .sidebar-menu-toggle:before {
+	/* hamburger button image */
 	content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" \
 		viewBox="0 0 22 22" fill="none" stroke="black">\
 		<path d="M3,5h16M3,11h16M3,17h16" stroke-width="2.75"/></svg>');
@@ -1847,6 +1877,7 @@ However, it's not needed with smaller screen width because the doc/code block is
 
 /* src sidebar button opens a folder view */
 .src #sidebar-button > a:before {
+	/* folder image */
 	content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" \
 		viewBox="0 0 22 22" fill="none" stroke="black">\
 		<path d="M16,9v-4h-6v-1l-2,-2h-4l-2,2v16h13L21,9h-15L2,19" stroke-width="1.25"/>\
@@ -2389,6 +2420,7 @@ by default.
 	--search-tab-button-not-selected-background: #e6e6e6;
 	--search-tab-button-selected-border-top-color: #0089ff;
 	--search-tab-button-selected-background: #fff;
+	--settings-menu-filter: none;
 	--stab-background-color: #fff5d6;
 	--stab-code-color: #000;
 	--code-highlight-kw-color: #8959a8;
@@ -2494,6 +2526,7 @@ by default.
 	--search-tab-button-not-selected-background: #252525;
 	--search-tab-button-selected-border-top-color: #0089ff;
 	--search-tab-button-selected-background: #353535;
+	--settings-menu-filter: none;
 	--stab-background-color: #314559;
 	--stab-code-color: #e6e1cf;
 	--code-highlight-kw-color: #ab8ac1;
@@ -2606,6 +2639,7 @@ Original by Dempfi (https://github.com/dempfi/ayu)
 	--search-tab-button-not-selected-background: transparent !important;
 	--search-tab-button-selected-border-top-color: none;
 	--search-tab-button-selected-background: #141920 !important;
+	--settings-menu-filter: invert(100%);
 	--stab-background-color: #314559;
 	--stab-code-color: #e6e1cf;
 	--code-highlight-kw-color: #ff7733;
diff --git a/src/librustdoc/html/static/images/clipboard.svg b/src/librustdoc/html/static/images/clipboard.svg
deleted file mode 100644
index e437c83fb6b..00000000000
--- a/src/librustdoc/html/static/images/clipboard.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="19" height="18" viewBox="0 0 24 25" xmlns="http://www.w3.org/2000/svg" aria-label="Copy to clipboard"><path d="M18 20h2v3c0 1-1 2-2 2H2c-.998 0-2-1-2-2V5c0-.911.755-1.667 1.667-1.667h5A3.323 3.323 0 0110 0a3.323 3.323 0 013.333 3.333h5C19.245 3.333 20 4.09 20 5v8.333h-2V9H2v14h16v-3zM3 7h14c0-.911-.793-1.667-1.75-1.667H13.5c-.957 0-1.75-.755-1.75-1.666C11.75 2.755 10.957 2 10 2s-1.75.755-1.75 1.667c0 .911-.793 1.666-1.75 1.666H4.75C3.793 5.333 3 6.09 3 7z"/><path d="M4 19h6v2H4zM12 11H4v2h8zM4 17h4v-2H4zM15 15v-3l-4.5 4.5L15 21v-3l8.027-.032L23 15z"/></svg>
diff --git a/src/librustdoc/html/static/images/wheel.svg b/src/librustdoc/html/static/images/wheel.svg
deleted file mode 100644
index ba30f13dd58..00000000000
--- a/src/librustdoc/html/static/images/wheel.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" enable-background="new 0 0 22 22" viewBox="0 0 27.434 29.5"><path d="M27.316 18.39a2.696 2.696 0 0 0-.98-1.46 1.62 1.62 0 0 1-.016-.762l.035-.176v-1.191c0-1.246-.003-1.278-.046-1.473a1.717 1.717 0 0 1 .007-.805c.477-.343.829-.859.997-1.472.257-.957.074-2.094-.508-3.117l-.594-1.032c-.746-1.304-1.965-2.117-3.18-2.117-.379 0-.75.078-1.086.235a1.958 1.958 0 0 1-.855-.391l-.102-.082-.117-.063-1.855-1.07-.094-.055-.106-.043c-.378-.156-.66-.41-.77-.554C17.919 1.172 16.349 0 14.297 0h-1.155c-2.043 0-3.61 1.152-3.75 2.723-.114.14-.391.382-.758.527l-.102.04-.094.05-1.94 1.066-.134.074-.117.094a2.019 2.019 0 0 1-.832.403 2.518 2.518 0 0 0-1.008-.211c-1.199 0-2.414.82-3.168 2.14l-.59 1.032c-.41.718-.64 1.523-.64 2.257-.004.953.36 1.758 1.012 2.258.035.152.058.445-.016.785-.04.168-.063.282-.063 1.563 0 1.148 0 1.148.016 1.261l.008.075.015.074c.075.344.047.64.012.8-.644.5-1.004 1.302-.992 2.259.008.726.238 1.52.648 2.242l.59 1.027c.758 1.332 1.965 2.16 3.149 2.16.324 0 .644-.062.937-.187.168.039.492.156.813.418l.11.086.124.07 2.047 1.156.102.059.105.043c.363.144.648.379.766.52.164 1.519 1.718 2.632 3.746 2.632h1.156c2.035 0 3.598-1.133 3.746-2.672.117-.144.402-.394.773-.55l.114-.047.101-.063 1.961-1.156.106-.063.097-.078c.309-.246.653-.37.832-.398.313.136.66.21 1.016.21 1.2 0 2.41-.82 3.164-2.14l.594-1.031c.59-1.028.777-2.164.52-3.117Zm-2.043 2.247-.59 1.031c-.437.766-1.105 1.25-1.636 1.25a.7.7 0 0 1-.371-.094 1.146 1.146 0 0 0-.567-.129c-.593 0-1.382.297-2.007.797l-1.961 1.156c-1.016.426-1.848 1.293-1.848 1.93 0 .64-.898 1.16-1.996 1.16H13.14c-1.102 0-2-.515-2-1.14 0-.63-.832-1.477-1.852-1.887l-2.047-1.16c-.637-.512-1.426-.813-2.008-.813-.199 0-.379.035-.515.114a.648.648 0 0 1-.332.085c-.52 0-1.18-.5-1.621-1.273l-.59-1.031c-.543-.953-.555-1.98-.024-2.285.532-.305.782-1.434.551-2.504V14.8c0-1.09.02-1.18.02-1.18.238-1.074-.008-2.203-.551-2.516-.54-.304-.54-1.34.008-2.293l.59-1.03c.437-.766 1.101-1.255 1.636-1.255a.73.73 0 0 1 .364.094c.152.086.343.125.566.125.594 0 1.379-.297 2.004-.793l1.945-1.066c1.02-.407 1.856-1.278 1.856-1.934 0-.656.898-1.191 2-1.191h1.156c1.098 0 1.996.543 1.996 1.21 0 .669.832 1.555 1.848 1.973L20 6.012c.617.492 1.402.777 2.012.777.242 0 .453-.047.62-.14a.79.79 0 0 1 .403-.102c.55 0 1.223.476 1.652 1.23l.59 1.032c.543.953.52 2.004-.062 2.336-.574.332-.86 1.48-.625 2.554 0 0 .008.04.008 1.102v1.011c-.215 1.051.07 2.176.636 2.5.567.325.586 1.368.04 2.325Zm0 0"/><path d="M13.61 7.61a7.084 7.084 0 0 0-7.083 7.085 7.085 7.085 0 1 0 14.168 0A7.088 7.088 0 0 0 13.61 7.61Zm0 12.41a5.33 5.33 0 0 1-5.325-5.325 5.33 5.33 0 0 1 5.324-5.32 5.327 5.327 0 0 1 5.325 5.32 5.328 5.328 0 0 1-5.325 5.325Zm0 0"/><path d="M13.684 9.906a4.722 4.722 0 0 0-4.72 4.719 4.722 4.722 0 0 0 4.72 4.719 4.724 4.724 0 0 0 4.714-4.719 4.724 4.724 0 0 0-4.714-4.719Zm0 7.676a2.954 2.954 0 1 1 0-5.91 2.953 2.953 0 0 1 2.953 2.953 2.957 2.957 0 0 1-2.953 2.957Zm0 0"/></svg>
\ No newline at end of file
diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js
index d24148b9556..8cebce7ae86 100644
--- a/src/librustdoc/html/static/js/externs.js
+++ b/src/librustdoc/html/static/js/externs.js
@@ -239,20 +239,27 @@ let FunctionType;
  * `doc` contains the description of the crate.
  *
  * `p` is a list of path/type pairs. It is used for parents and function parameters.
+ * The first item is the type, the second is the name, the third is the visible path (if any) and
+ * the fourth is the canonical path used for deduplication (if any).
+ *
+ * `r` is the canonical path used for deduplication of re-exported items.
+ * It is not used for associated items like methods (that's the fourth element
+ * of `p`) but is used for modules items like free functions.
  *
  * `c` is an array of item indices that are deprecated.
  * @typedef {{
  *   doc: string,
  *   a: Object,
  *   n: Array<string>,
- *   t: String,
+ *   t: string,
  *   d: Array<string>,
- *   q: Array<[Number, string]>,
- *   i: Array<Number>,
+ *   q: Array<[number, string]>,
+ *   i: Array<number>,
  *   f: string,
- *   p: Array<Object>,
- *   b: Array<[Number, String]>,
- *   c: Array<Number>
+ *   p: Array<[number, string] | [number, string, number] | [number, string, number, number]>,
+ *   b: Array<[number, String]>,
+ *   c: Array<number>,
+ *   r: Array<[number, number]>,
  * }}
  */
 let RawSearchIndexCrate;
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index ee7d19634b4..64c35660778 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -1798,31 +1798,15 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/read-documentation/search.htm
         document.execCommand("copy");
         document.body.removeChild(el);
 
-        // There is always one children, but multiple childNodes.
-        but.children[0].style.display = "none";
-
-        let tmp;
-        if (but.childNodes.length < 2) {
-            tmp = document.createTextNode("✓");
-            but.appendChild(tmp);
-        } else {
-            onEachLazy(but.childNodes, e => {
-                if (e.nodeType === Node.TEXT_NODE) {
-                    tmp = e;
-                    return true;
-                }
-            });
-            tmp.textContent = "✓";
-        }
+        but.classList.add("clicked");
 
         if (reset_button_timeout !== null) {
             window.clearTimeout(reset_button_timeout);
         }
 
         function reset_button() {
-            tmp.textContent = "";
             reset_button_timeout = null;
-            but.children[0].style.display = "";
+            but.classList.remove("clicked");
         }
 
         reset_button_timeout = window.setTimeout(reset_button, 1000);
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index 3daf1ad22de..76a6fc9008e 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -79,6 +79,7 @@ const longItemTypes = [
 
 // used for special search precedence
 const TY_GENERIC = itemTypes.indexOf("generic");
+const TY_IMPORT = itemTypes.indexOf("import");
 const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";
 
 // Hard limit on how deep to recurse into generics when doing type-driven search.
@@ -785,6 +786,37 @@ function initSearch(rawSearchIndex) {
                 }
                 elems.push(makePrimitiveElement(name, { bindingName, generics }));
             }
+        } else if (parserState.userQuery[parserState.pos] === "&") {
+            if (parserState.typeFilter !== null && parserState.typeFilter !== "primitive") {
+                throw [
+                    "Invalid search type: primitive ",
+                    "&",
+                    " and ",
+                    parserState.typeFilter,
+                    " both specified",
+                ];
+            }
+            parserState.typeFilter = null;
+            parserState.pos += 1;
+            let c = parserState.userQuery[parserState.pos];
+            while (c === " " && parserState.pos < parserState.length) {
+                parserState.pos += 1;
+                c = parserState.userQuery[parserState.pos];
+            }
+            const generics = [];
+            if (parserState.userQuery.slice(parserState.pos, parserState.pos + 3) === "mut") {
+                generics.push(makePrimitiveElement("mut", { typeFilter: "keyword"}));
+                parserState.pos += 3;
+                c = parserState.userQuery[parserState.pos];
+            }
+            while (c === " " && parserState.pos < parserState.length) {
+                parserState.pos += 1;
+                c = parserState.userQuery[parserState.pos];
+            }
+            if (!isEndCharacter(c) && parserState.pos < parserState.length) {
+                getFilteredNextElem(query, parserState, generics, isInGenerics);
+            }
+            elems.push(makePrimitiveElement("reference", { generics }));
         } else {
             const isStringElem = parserState.userQuery[start] === "\"";
             // We handle the strings on their own mostly to make code easier to follow.
@@ -1324,14 +1356,23 @@ function initSearch(rawSearchIndex) {
                     obj.dist = result.dist;
                     const res = buildHrefAndPath(obj);
                     obj.displayPath = pathSplitter(res[0]);
-                    obj.fullPath = obj.displayPath + obj.name;
-                    // To be sure than it some items aren't considered as duplicate.
-                    obj.fullPath += "|" + obj.ty;
 
+                    // To be sure than it some items aren't considered as duplicate.
+                    obj.fullPath = res[2] + "|" + obj.ty;
                     if (duplicates.has(obj.fullPath)) {
                         continue;
                     }
+
+                    // Exports are specifically not shown if the items they point at
+                    // are already in the results.
+                    if (obj.ty === TY_IMPORT && duplicates.has(res[2])) {
+                        continue;
+                    }
+                    if (duplicates.has(res[2] + "|" + TY_IMPORT)) {
+                        continue;
+                    }
                     duplicates.add(obj.fullPath);
+                    duplicates.add(res[2]);
 
                     obj.href = res[1];
                     out.push(obj);
@@ -1454,16 +1495,7 @@ function initSearch(rawSearchIndex) {
                 return 0;
             });
 
-            const transformed = transformResults(result_list);
-            const descs = await Promise.all(transformed.map(result => {
-                return searchIndexEmptyDesc.get(result.crate).contains(result.bitIndex) ?
-                    "" :
-                    searchState.loadDesc(result);
-            }));
-            for (const [i, result] of transformed.entries()) {
-                result.desc = descs[i];
-            }
-            return transformed;
+            return transformResults(result_list);
         }
 
         /**
@@ -2085,6 +2117,7 @@ function initSearch(rawSearchIndex) {
                 path: item.path,
                 descShard: item.descShard,
                 descIndex: item.descIndex,
+                exactPath: item.exactPath,
                 ty: item.ty,
                 parent: item.parent,
                 type: item.type,
@@ -2506,6 +2539,16 @@ function initSearch(rawSearchIndex) {
             sorted_others,
             parsedQuery);
         handleAliases(ret, parsedQuery.original.replace(/"/g, ""), filterCrates, currentCrate);
+        await Promise.all([ret.others, ret.returned, ret.in_args].map(async list => {
+            const descs = await Promise.all(list.map(result => {
+                return searchIndexEmptyDesc.get(result.crate).contains(result.bitIndex) ?
+                    "" :
+                    searchState.loadDesc(result);
+            }));
+            for (const [i, result] of list.entries()) {
+                result.desc = descs[i];
+            }
+        }));
         if (parsedQuery.error !== null && ret.others.length !== 0) {
             // It means some doc aliases were found so let's "remove" the error!
             ret.query.error = null;
@@ -2538,6 +2581,7 @@ function initSearch(rawSearchIndex) {
         const type = itemTypes[item.ty];
         const name = item.name;
         let path = item.path;
+        let exactPath = item.exactPath;
 
         if (type === "mod") {
             displayPath = path + "::";
@@ -2559,6 +2603,7 @@ function initSearch(rawSearchIndex) {
             const parentType = itemTypes[myparent.ty];
             let pageType = parentType;
             let pageName = myparent.name;
+            exactPath = `${myparent.exactPath}::${myparent.name}`;
 
             if (parentType === "primitive") {
                 displayPath = myparent.name + "::";
@@ -2587,7 +2632,7 @@ function initSearch(rawSearchIndex) {
             href = ROOT_PATH + item.path.replace(/::/g, "/") +
                 "/" + type + "." + name + ".html";
         }
-        return [displayPath, href];
+        return [displayPath, href, `${exactPath}::${name}`];
     }
 
     function pathSplitter(path) {
@@ -2980,6 +3025,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
                 id: pathIndex,
                 ty: TY_GENERIC,
                 path: null,
+                exactPath: null,
                 generics,
                 bindings,
             };
@@ -2989,6 +3035,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
                 id: null,
                 ty: null,
                 path: null,
+                exactPath: null,
                 generics,
                 bindings,
             };
@@ -2998,6 +3045,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
                 id: buildTypeMapIndex(item.name, isAssocType),
                 ty: item.ty,
                 path: item.path,
+                exactPath: item.exactPath,
                 generics,
                 bindings,
             };
@@ -3453,6 +3501,8 @@ ${item.displayPath}<span class="${type}">${name}</span>\
                 path: "",
                 descShard,
                 descIndex,
+                exactPath: "",
+                desc: crateCorpus.doc,
                 parent: undefined,
                 type: null,
                 id,
@@ -3478,6 +3528,9 @@ ${item.displayPath}<span class="${type}">${name}</span>\
             // i.e. if indices 4 and 11 are present, but 5-10 and 12-13 are not present,
             // 5-10 will fall back to the path for 4 and 12-13 will fall back to the path for 11
             const itemPaths = new Map(crateCorpus.q);
+            // An array of [(Number) item index, (Number) path index]
+            // Used to de-duplicate inlined and re-exported stuff
+            const itemReexports = new Map(crateCorpus.r);
             // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
             const itemParentIdxs = crateCorpus.i;
             // a map Number, string for impl disambiguators
@@ -3511,9 +3564,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
                     path = itemPaths.has(elem[2]) ? itemPaths.get(elem[2]) : lastPath;
                     lastPath = path;
                 }
+                const exactPath = elem.length > 3 ? itemPaths.get(elem[3]) : path;
 
-                lowercasePaths.push({ty: ty, name: name.toLowerCase(), path: path});
-                paths[i] = {ty: ty, name: name, path: path};
+                lowercasePaths.push({ty, name: name.toLowerCase(), path, exactPath});
+                paths[i] = {ty, name, path, exactPath};
             }
 
             // convert `item*` into an object form, and construct word indices.
@@ -3572,6 +3626,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
                     path,
                     descShard,
                     descIndex,
+                    exactPath: itemReexports.has(i) ? itemPaths.get(itemReexports.get(i)) : path,
                     parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
                     type,
                     id,
diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js
index 73c543567c0..4a27ca92fff 100644
--- a/src/librustdoc/html/static/js/storage.js
+++ b/src/librustdoc/html/static/js/storage.js
@@ -239,3 +239,46 @@ window.addEventListener("pageshow", ev => {
         setTimeout(updateSidebarWidth, 0);
     }
 });
+
+// Custom elements are used to insert some JS-dependent features into Rustdoc,
+// because the [parser] runs the connected callback
+// synchronously. It needs to be added synchronously so that nothing below it
+// becomes visible until after it's done. Otherwise, you get layout jank.
+//
+// That's also why this is in storage.js and not main.js.
+//
+// [parser]: https://html.spec.whatwg.org/multipage/parsing.html
+class RustdocSearchElement extends HTMLElement {
+    constructor() {
+        super();
+    }
+    connectedCallback() {
+        const rootPath = getVar("root-path");
+        const currentCrate = getVar("current-crate");
+        this.innerHTML = `<nav class="sub">
+            <form class="search-form">
+                <span></span> <!-- This empty span is a hacky fix for Safari - See #93184 -->
+                <div id="sidebar-button" tabindex="-1">
+                    <a href="${rootPath}${currentCrate}/all.html" title="show sidebar"></a>
+                </div>
+                <input
+                    class="search-input"
+                    name="search"
+                    aria-label="Run search in the documentation"
+                    autocomplete="off"
+                    spellcheck="false"
+                    placeholder="Type ‘S’ or ‘/’ to search, ‘?’ for more options…"
+                    type="search">
+                <div id="help-button" tabindex="-1">
+                    <a href="${rootPath}help.html" title="help">?</a>
+                </div>
+                <div id="settings-menu" tabindex="-1">
+                    <a href="${rootPath}settings.html" title="settings">
+                        Settings
+                    </a>
+                </div>
+            </form>
+        </nav>`;
+    }
+}
+window.customElements.define("rustdoc-search", RustdocSearchElement);
diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs
index d8874c2fda0..035376bace9 100644
--- a/src/librustdoc/html/static_files.rs
+++ b/src/librustdoc/html/static_files.rs
@@ -99,8 +99,6 @@ static_files! {
     src_script_js => "static/js/src-script.js",
     storage_js => "static/js/storage.js",
     scrape_examples_js => "static/js/scrape-examples.js",
-    wheel_svg => "static/images/wheel.svg",
-    clipboard_svg => "static/images/clipboard.svg",
     copyright => "static/COPYRIGHT.txt",
     license_apache => "static/LICENSE-APACHE.txt",
     license_mit => "static/LICENSE-MIT.txt",
diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html
index 1e01cd70b96..cdf01fa7a97 100644
--- a/src/librustdoc/html/templates/page.html
+++ b/src/librustdoc/html/templates/page.html
@@ -117,30 +117,9 @@
     <div class="sidebar-resizer"></div> {# #}
     <main> {# #}
         {% if page.css_class != "src" %}<div class="width-limiter">{% endif %}
-            <nav class="sub"> {# #}
-                <form class="search-form"> {# #}
-                    <span></span> {# This empty span is a hacky fix for Safari - See #93184 #}
-                    <div id="sidebar-button" tabindex="-1"> {# #}
-                        <a href="{{page.root_path|safe}}{{layout.krate|safe}}/all.html" title="show sidebar"></a> {# #}
-                    </div> {# #}
-                    <input {#+ #}
-                        class="search-input" {#+ #}
-                        name="search" {#+ #}
-                        aria-label="Run search in the documentation" {#+ #}
-                        autocomplete="off" {#+ #}
-                        spellcheck="false" {#+ #}
-                        placeholder="Type ‘S’ or ‘/’ to search, ‘?’ for more options…" {#+ #}
-                        type="search"> {# #}
-                    <div id="help-button" tabindex="-1"> {# #}
-                        <a href="{{page.root_path|safe}}help.html" title="help">?</a> {# #}
-                    </div> {# #}
-                    <div id="settings-menu" tabindex="-1"> {# #}
-                        <a href="{{page.root_path|safe}}settings.html" title="settings"> {# #}
-                            Settings {# #}
-                        </a> {# #}
-                    </div> {# #}
-                </form> {# #}
-            </nav> {# #}
+            {# defined in storage.js to avoid duplicating complex UI across every page #}
+            {# and because the search form only works if JS is enabled anyway #}
+            <rustdoc-search></rustdoc-search> {# #}
             <section id="main-content" class="content">{{ content|safe }}</section> {# #}
         {% if page.css_class != "src" %}</div>{% endif %}
     </main> {# #}