about summary refs log tree commit diff
diff options
context:
space:
mode:
authorManish Goregaokar <manishsmail@gmail.com>2022-11-11 12:12:31 -0500
committerGitHub <noreply@github.com>2022-11-11 12:12:31 -0500
commitb74d042be2c237fa1a29e87ed7501410974a0dd9 (patch)
tree0c60b65b40f22a4a1ed81f14bd6aa5e16273a872
parente8bc6e132820f24909f4ef536f84e0aa3ddf2d2b (diff)
parent05824cd7b7f9b022a68f49b676a58088b6b85118 (diff)
downloadrust-b74d042be2c237fa1a29e87ed7501410974a0dd9.tar.gz
rust-b74d042be2c237fa1a29e87ed7501410974a0dd9.zip
Rollup merge of #104129 - notriddle:notriddle/102576-js-notable-trait, r=Manishearth
rustdoc: use javascript to layout notable traits popups

Fixes #102576

Preview: https://notriddle.com/notriddle-rustdoc-demos/102576-js-notable-trait/std/iter/trait.Iterator.html#method.step_by

## Before

![image](https://user-images.githubusercontent.com/1593513/200432713-23aa8d49-ba2b-469f-9eab-089e0c174039.png)

## After

![image](https://user-images.githubusercontent.com/1593513/200432735-edd5b53d-e99d-4fc5-9410-8a79e0efdc9d.png)
-rw-r--r--src/librustdoc/html/format.rs4
-rw-r--r--src/librustdoc/html/render/context.rs7
-rw-r--r--src/librustdoc/html/render/mod.rs195
-rw-r--r--src/librustdoc/html/render/print_item.rs24
-rw-r--r--src/librustdoc/html/static/css/noscript.css6
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css23
-rw-r--r--src/librustdoc/html/static/js/main.js87
-rw-r--r--src/test/rustdoc-gui/notable-trait.goml102
-rw-r--r--src/test/rustdoc/doc-notable_trait-slice.bare_fn_matches.html1
-rw-r--r--src/test/rustdoc/doc-notable_trait-slice.rs4
-rw-r--r--src/test/rustdoc/doc-notable_trait.bare-fn.html1
-rw-r--r--src/test/rustdoc/doc-notable_trait.rs10
-rw-r--r--src/test/rustdoc/doc-notable_trait.some-struct-new.html1
-rw-r--r--src/test/rustdoc/doc-notable_trait.wrap-me.html1
-rw-r--r--src/test/rustdoc/spotlight-from-dependency.odd.html1
-rw-r--r--src/test/rustdoc/spotlight-from-dependency.rs3
16 files changed, 360 insertions, 110 deletions
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index a5c3d35b1b5..39e2a902226 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -107,10 +107,6 @@ impl Buffer {
         self.buffer
     }
 
-    pub(crate) fn insert_str(&mut self, idx: usize, s: &str) {
-        self.buffer.insert_str(idx, s);
-    }
-
     pub(crate) fn push_str(&mut self, s: &str) {
         self.buffer.push_str(s);
     }
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 51843a505f7..73690c86f4f 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -69,11 +69,13 @@ pub(crate) struct Context<'tcx> {
     /// the source files are present in the html rendering, then this will be
     /// `true`.
     pub(crate) include_sources: bool,
+    /// Collection of all types with notable traits referenced in the current module.
+    pub(crate) types_with_notable_traits: FxHashSet<clean::Type>,
 }
 
 // `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"))]
-rustc_data_structures::static_assert_size!(Context<'_>, 128);
+rustc_data_structures::static_assert_size!(Context<'_>, 160);
 
 /// Shared mutable state used in [`Context`] and elsewhere.
 pub(crate) struct SharedContext<'tcx> {
@@ -532,6 +534,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             deref_id_map: FxHashMap::default(),
             shared: Rc::new(scx),
             include_sources,
+            types_with_notable_traits: FxHashSet::default(),
         };
 
         if emit_crate {
@@ -560,6 +563,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             id_map: IdMap::new(),
             shared: Rc::clone(&self.shared),
             include_sources: self.include_sources,
+            types_with_notable_traits: FxHashSet::default(),
         }
     }
 
@@ -803,6 +807,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
                 }
             }
         }
+
         Ok(())
     }
 
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 3a041ae15d6..266ec2ac7ad 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -59,7 +59,7 @@ use rustc_span::{
     symbol::{sym, Symbol},
     BytePos, FileName, RealFileName,
 };
-use serde::ser::SerializeSeq;
+use serde::ser::{SerializeMap, SerializeSeq};
 use serde::{Serialize, Serializer};
 
 use crate::clean::{self, ItemId, RenderedLink, SelfTy};
@@ -803,7 +803,7 @@ fn assoc_method(
     d: &clean::FnDecl,
     link: AssocItemLink<'_>,
     parent: ItemType,
-    cx: &Context<'_>,
+    cx: &mut Context<'_>,
     render_mode: RenderMode,
 ) {
     let tcx = cx.tcx();
@@ -836,6 +836,8 @@ fn assoc_method(
         + name.as_str().len()
         + generics_len;
 
+    let notable_traits = d.output.as_return().and_then(|output| notable_traits_button(output, cx));
+
     let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
         header_len += 4;
         let indent_str = "    ";
@@ -861,9 +863,9 @@ fn assoc_method(
         name = name,
         generics = g.print(cx),
         decl = d.full_print(header_len, indent, cx),
-        notable_traits = notable_traits_decl(d, cx),
+        notable_traits = notable_traits.unwrap_or_default(),
         where_clause = print_where_clause(g, cx, indent, end_newline),
-    )
+    );
 }
 
 /// Writes a span containing the versions at which an item became stable and/or const-stable. For
@@ -963,7 +965,7 @@ fn render_assoc_item(
     item: &clean::Item,
     link: AssocItemLink<'_>,
     parent: ItemType,
-    cx: &Context<'_>,
+    cx: &mut Context<'_>,
     render_mode: RenderMode,
 ) {
     match &*item.kind {
@@ -1273,88 +1275,135 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) ->
     }
 }
 
-fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String {
-    let mut out = Buffer::html();
+pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> Option<String> {
+    let mut has_notable_trait = false;
+
+    let did = ty.def_id(cx.cache())?;
 
-    if let Some((did, ty)) = decl.output.as_return().and_then(|t| Some((t.def_id(cx.cache())?, t)))
+    // Box has pass-through impls for Read, Write, Iterator, and Future when the
+    // boxed type implements one of those. We don't want to treat every Box return
+    // as being notably an Iterator (etc), though, so we exempt it. Pin has the same
+    // issue, with a pass-through impl for Future.
+    if Some(did) == cx.tcx().lang_items().owned_box()
+        || Some(did) == cx.tcx().lang_items().pin_type()
     {
-        // Box has pass-through impls for Read, Write, Iterator, and Future when the
-        // boxed type implements one of those. We don't want to treat every Box return
-        // as being notably an Iterator (etc), though, so we exempt it. Pin has the same
-        // issue, with a pass-through impl for Future.
-        if Some(did) == cx.tcx().lang_items().owned_box()
-            || Some(did) == cx.tcx().lang_items().pin_type()
-        {
-            return "".to_string();
-        }
-        if let Some(impls) = cx.cache().impls.get(&did) {
-            for i in impls {
-                let impl_ = i.inner_impl();
-                if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache())
+        return None;
+    }
+
+    if let Some(impls) = cx.cache().impls.get(&did) {
+        for i in impls {
+            let impl_ = i.inner_impl();
+            if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache()) {
+                // Two different types might have the same did,
+                // without actually being the same.
+                continue;
+            }
+            if let Some(trait_) = &impl_.trait_ {
+                let trait_did = trait_.def_id();
+
+                if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable_trait(cx.tcx()))
                 {
-                    // Two different types might have the same did,
-                    // without actually being the same.
-                    continue;
+                    has_notable_trait = true;
+                }
+            }
+        }
+    }
+
+    if has_notable_trait {
+        cx.types_with_notable_traits.insert(ty.clone());
+        Some(format!(
+            "<span class=\"notable-traits\" data-ty=\"{ty}\">\
+                <span class=\"notable-traits-tooltip\">ⓘ</span>\
+            </span>",
+            ty = Escape(&format!("{:#}", ty.print(cx))),
+        ))
+    } else {
+        None
+    }
+}
+
+fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
+    let mut out = Buffer::html();
+
+    let did = ty.def_id(cx.cache()).expect("notable_traits_button already checked this");
+
+    let impls = cx.cache().impls.get(&did).expect("notable_traits_button already checked this");
+
+    for i in impls {
+        let impl_ = i.inner_impl();
+        if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache()) {
+            // Two different types might have the same did,
+            // without actually being the same.
+            continue;
+        }
+        if let Some(trait_) = &impl_.trait_ {
+            let trait_did = trait_.def_id();
+
+            if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable_trait(cx.tcx())) {
+                if out.is_empty() {
+                    write!(
+                        &mut out,
+                        "<h3 class=\"notable\">Notable traits for <code>{}</code></h3>\
+                     <pre class=\"content\"><code>",
+                        impl_.for_.print(cx)
+                    );
                 }
-                if let Some(trait_) = &impl_.trait_ {
-                    let trait_did = trait_.def_id();
-
-                    if cx
-                        .cache()
-                        .traits
-                        .get(&trait_did)
-                        .map_or(false, |t| t.is_notable_trait(cx.tcx()))
-                    {
-                        if out.is_empty() {
-                            write!(
-                                &mut out,
-                                "<span class=\"notable\">Notable traits for {}</span>\
-                             <code class=\"content\">",
-                                impl_.for_.print(cx)
-                            );
-                        }
 
-                        //use the "where" class here to make it small
-                        write!(
+                //use the "where" class here to make it small
+                write!(
+                    &mut out,
+                    "<span class=\"where fmt-newline\">{}</span>",
+                    impl_.print(false, cx)
+                );
+                for it in &impl_.items {
+                    if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
+                        out.push_str("<span class=\"where fmt-newline\">    ");
+                        let empty_set = FxHashSet::default();
+                        let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set);
+                        assoc_type(
                             &mut out,
-                            "<span class=\"where fmt-newline\">{}</span>",
-                            impl_.print(false, cx)
+                            it,
+                            &tydef.generics,
+                            &[], // intentionally leaving out bounds
+                            Some(&tydef.type_),
+                            src_link,
+                            0,
+                            cx,
                         );
-                        for it in &impl_.items {
-                            if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
-                                out.push_str("<span class=\"where fmt-newline\">    ");
-                                let empty_set = FxHashSet::default();
-                                let src_link =
-                                    AssocItemLink::GotoSource(trait_did.into(), &empty_set);
-                                assoc_type(
-                                    &mut out,
-                                    it,
-                                    &tydef.generics,
-                                    &[], // intentionally leaving out bounds
-                                    Some(&tydef.type_),
-                                    src_link,
-                                    0,
-                                    cx,
-                                );
-                                out.push_str(";</span>");
-                            }
-                        }
+                        out.push_str(";</span>");
                     }
                 }
             }
         }
     }
-
-    if !out.is_empty() {
-        out.insert_str(
-            0,
-            "<span class=\"notable-traits\"><span class=\"notable-traits-tooltip\">ⓘ\
-            <span class=\"notable-traits-tooltiptext\"><span class=\"docblock\">",
-        );
-        out.push_str("</code></span></span></span></span>");
+    if out.is_empty() {
+        write!(&mut out, "</code></pre>",);
     }
 
-    out.into_inner()
+    (format!("{:#}", ty.print(cx)), out.into_inner())
+}
+
+pub(crate) fn notable_traits_json<'a>(
+    tys: impl Iterator<Item = &'a clean::Type>,
+    cx: &Context<'_>,
+) -> String {
+    let mut mp: Vec<(String, String)> = tys.map(|ty| notable_traits_decl(ty, cx)).collect();
+    mp.sort_by(|(name1, _html1), (name2, _html2)| name1.cmp(name2));
+    struct NotableTraitsMap(Vec<(String, String)>);
+    impl Serialize for NotableTraitsMap {
+        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+        where
+            S: Serializer,
+        {
+            let mut map = serializer.serialize_map(Some(self.0.len()))?;
+            for item in &self.0 {
+                map.serialize_entry(&item.0, &item.1)?;
+            }
+            map.end()
+        }
+    }
+    serde_json::to_string(&NotableTraitsMap(mp))
+        .expect("serialize (string, string) -> json object cannot fail")
 }
 
 #[derive(Clone, Copy, Debug)]
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index ce4fc4d68fa..ac11a860a4f 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -17,9 +17,10 @@ use std::rc::Rc;
 
 use super::{
     collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference,
-    item_ty_to_section, notable_traits_decl, render_all_impls, render_assoc_item,
-    render_assoc_items, render_attributes_in_code, render_attributes_in_pre, render_impl,
-    render_rightside, render_stability_since_raw, AssocItemLink, Context, ImplRenderingParameters,
+    item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls,
+    render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre,
+    render_impl, render_rightside, render_stability_since_raw, AssocItemLink, Context,
+    ImplRenderingParameters,
 };
 use crate::clean;
 use crate::config::ModuleSorting;
@@ -183,6 +184,16 @@ pub(super) fn print_item(
             unreachable!();
         }
     }
+
+    // Render notable-traits.js used for all methods in this module.
+    if !cx.types_with_notable_traits.is_empty() {
+        write!(
+            buf,
+            r#"<script type="text/json" id="notable-traits-data">{}</script>"#,
+            notable_traits_json(cx.types_with_notable_traits.iter(), cx)
+        );
+        cx.types_with_notable_traits.clear();
+    }
 }
 
 /// For large structs, enums, unions, etc, determine whether to hide their fields
@@ -516,6 +527,9 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
         + name.as_str().len()
         + generics_len;
 
+    let notable_traits =
+        f.decl.output.as_return().and_then(|output| notable_traits_button(output, cx));
+
     wrap_into_item_decl(w, |w| {
         wrap_item(w, "fn", |w| {
             render_attributes_in_pre(w, it, "");
@@ -533,11 +547,11 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
                 generics = f.generics.print(cx),
                 where_clause = print_where_clause(&f.generics, cx, 0, Ending::Newline),
                 decl = f.decl.full_print(header_len, 0, cx),
-                notable_traits = notable_traits_decl(&f.decl, cx),
+                notable_traits = notable_traits.unwrap_or_default(),
             );
         });
     });
-    document(w, cx, it, None, HeadingOffset::H2)
+    document(w, cx, it, None, HeadingOffset::H2);
 }
 
 fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Trait) {
diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css
index 301f03a1642..54e8b6561f3 100644
--- a/src/librustdoc/html/static/css/noscript.css
+++ b/src/librustdoc/html/static/css/noscript.css
@@ -22,3 +22,9 @@ nav.sub {
 .source .sidebar {
 	display: none;
 }
+
+.notable-traits {
+	/* layout requires javascript
+	    https://github.com/rust-lang/rust/issues/102576 */
+	display: none;
+}
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 86a5b0b303f..9a30240c7ba 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -183,6 +183,8 @@ h4.code-header {
 	font-weight: 600;
 	margin: 0;
 	padding: 0;
+	/* position notable traits in mobile mode within the header */
+	position: relative;
 }
 
 #crate-search,
@@ -1276,13 +1278,12 @@ h3.variant {
 	cursor: pointer;
 }
 
-.notable-traits:hover .notable-traits-tooltiptext,
-.notable-traits .notable-traits-tooltiptext.force-tooltip {
+.notable-traits .notable-traits-tooltiptext {
 	display: inline-block;
+	visibility: hidden;
 }
 
-.notable-traits .notable-traits-tooltiptext {
-	display: none;
+.notable-traits-tooltiptext {
 	padding: 5px 3px 3px 3px;
 	border-radius: 6px;
 	margin-left: 5px;
@@ -1300,22 +1301,26 @@ h3.variant {
 	content: "\00a0\00a0\00a0";
 }
 
-.notable-traits .docblock {
+.notable-traits-tooltiptext .docblock {
 	margin: 0;
 }
 
-.notable-traits .notable {
-	margin: 0;
-	margin-bottom: 13px;
+.notable-traits-tooltiptext .notable {
 	font-size: 1.1875rem;
 	font-weight: 600;
 	display: block;
 }
 
-.notable-traits .docblock code.content {
+.notable-traits-tooltiptext pre, .notable-traits-tooltiptext code {
+	background: transparent;
+}
+
+.notable-traits-tooltiptext .docblock pre.content {
 	margin: 0;
 	padding: 0;
 	font-size: 1.25rem;
+	white-space: pre-wrap;
+	overflow: hidden;
 }
 
 .search-failed {
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index 1c84393cb4e..0426774e80d 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -790,6 +790,19 @@ function loadCss(cssUrl) {
             // we need to switch away from mobile mode and make the main content area scrollable.
             hideSidebar();
         }
+        if (window.CURRENT_NOTABLE_ELEMENT) {
+            // As a workaround to the behavior of `contains: layout` used in doc togglers, the
+            // notable traits popup is positioned using javascript.
+            //
+            // This means when the window is resized, we need to redo the layout.
+            const base = window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE;
+            const force_visible = base.NOTABLE_FORCE_VISIBLE;
+            hideNotable();
+            if (force_visible) {
+                showNotable(base);
+                base.NOTABLE_FORCE_VISIBLE = true;
+            }
+        }
     });
 
     function handleClick(id, f) {
@@ -822,10 +835,80 @@ function loadCss(cssUrl) {
         });
     });
 
+    function showNotable(e) {
+        if (!window.NOTABLE_TRAITS) {
+            const data = document.getElementById("notable-traits-data");
+            if (data) {
+                window.NOTABLE_TRAITS = JSON.parse(data.innerText);
+            } else {
+                throw new Error("showNotable() called on page without any notable traits!");
+            }
+        }
+        if (window.CURRENT_NOTABLE_ELEMENT && window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE === e) {
+            // Make this function idempotent.
+            return;
+        }
+        hideNotable();
+        const ty = e.getAttribute("data-ty");
+        const tooltip = e.getElementsByClassName("notable-traits-tooltip")[0];
+        const wrapper = document.createElement("div");
+        wrapper.innerHTML = "<div class=\"docblock\">" + window.NOTABLE_TRAITS[ty] + "</div>";
+        wrapper.className = "notable-traits-tooltiptext";
+        tooltip.appendChild(wrapper);
+        const pos = wrapper.getBoundingClientRect();
+        tooltip.removeChild(wrapper);
+        wrapper.style.top = (pos.top + window.scrollY) + "px";
+        wrapper.style.left = (pos.left + window.scrollX) + "px";
+        wrapper.style.width = pos.width + "px";
+        const body = document.getElementsByTagName("body")[0];
+        body.appendChild(wrapper);
+        window.CURRENT_NOTABLE_ELEMENT = wrapper;
+        window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE = e;
+        wrapper.onpointerleave = function(ev) {
+            // If this is a synthetic touch event, ignore it. A click event will be along shortly.
+            if (ev.pointerType !== "mouse") {
+                return;
+            }
+            if (!e.NOTABLE_FORCE_VISIBLE && !elemIsInParent(event.relatedTarget, e)) {
+                hideNotable();
+            }
+        };
+    }
+
+    function hideNotable() {
+        if (window.CURRENT_NOTABLE_ELEMENT) {
+            window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.NOTABLE_FORCE_VISIBLE = false;
+            const body = document.getElementsByTagName("body")[0];
+            body.removeChild(window.CURRENT_NOTABLE_ELEMENT);
+            window.CURRENT_NOTABLE_ELEMENT = null;
+        }
+    }
+
     onEachLazy(document.getElementsByClassName("notable-traits"), e => {
         e.onclick = function() {
-            this.getElementsByClassName("notable-traits-tooltiptext")[0]
-                .classList.toggle("force-tooltip");
+            this.NOTABLE_FORCE_VISIBLE = this.NOTABLE_FORCE_VISIBLE ? false : true;
+            if (window.CURRENT_NOTABLE_ELEMENT && !this.NOTABLE_FORCE_VISIBLE) {
+                hideNotable();
+            } else {
+                showNotable(this);
+            }
+        };
+        e.onpointerenter = function(ev) {
+            // If this is a synthetic touch event, ignore it. A click event will be along shortly.
+            if (ev.pointerType !== "mouse") {
+                return;
+            }
+            showNotable(this);
+        };
+        e.onpointerleave = function(ev) {
+            // If this is a synthetic touch event, ignore it. A click event will be along shortly.
+            if (ev.pointerType !== "mouse") {
+                return;
+            }
+            if (!this.NOTABLE_FORCE_VISIBLE &&
+                !elemIsInParent(event.relatedTarget, window.CURRENT_NOTABLE_ELEMENT)) {
+                hideNotable();
+            }
         };
     });
 
diff --git a/src/test/rustdoc-gui/notable-trait.goml b/src/test/rustdoc-gui/notable-trait.goml
index efe0cb15f08..d8261d8dc90 100644
--- a/src/test/rustdoc-gui/notable-trait.goml
+++ b/src/test/rustdoc-gui/notable-trait.goml
@@ -25,22 +25,28 @@ assert-position: (
     {"x": 951},
 )
 // The tooltip should be beside the `i`
+// Also, clicking the tooltip should bring its text into the DOM
+assert-count: ("//*[@class='notable-traits-tooltiptext']", 0)
 click: "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']"
+assert-count: ("//*[@class='notable-traits-tooltiptext']", 1)
 compare-elements-position-near: (
     "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']",
-    "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits-tooltiptext force-tooltip']",
+    "//*[@class='notable-traits-tooltiptext']",
     {"y": 2}
 )
 compare-elements-position-false: (
     "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']",
-    "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits-tooltiptext force-tooltip']",
+    "//*[@class='notable-traits-tooltiptext']",
     ("x")
 )
 // The docblock should be flush with the border.
 assert-css: (
-    "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits-tooltiptext force-tooltip']/*[@class='docblock']",
+    "//*[@class='notable-traits-tooltiptext']/*[@class='docblock']",
     {"margin-left": "0px"}
 )
+click: "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']"
+move-cursor-to: "//h1"
+assert-count: ("//*[@class='notable-traits-tooltiptext']", 0)
 
 // Now only the `i` should be on the next line.
 size: (1055, 600)
@@ -98,26 +104,31 @@ assert-position: (
     {"x": 289},
 )
 // The tooltip should be below `i`
+click: "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']"
+assert-count: ("//*[@class='notable-traits-tooltiptext']", 1)
 compare-elements-position-near-false: (
     "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']",
-    "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits-tooltiptext force-tooltip']",
+    "//*[@class='notable-traits-tooltiptext']",
     {"y": 2}
 )
 compare-elements-position-false: (
     "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']",
-    "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits-tooltiptext force-tooltip']",
+    "//*[@class='notable-traits-tooltiptext']",
     ("x")
 )
 compare-elements-position-near: (
-    "//*[@id='method.create_an_iterator_from_read']/parent::*",
-    "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits-tooltiptext force-tooltip']",
-    {"x": 5}
+    "//*[@id='method.create_an_iterator_from_read']",
+    "//*[@class='notable-traits-tooltiptext']",
+    {"x": 10}
 )
 // The docblock should be flush with the border.
 assert-css: (
-    "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits-tooltiptext force-tooltip']/*[@class='docblock']",
+    "//*[@class='notable-traits-tooltiptext']/*[@class='docblock']",
     {"margin-left": "0px"}
 )
+click: "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']"
+move-cursor-to: "//h1"
+assert-count: ("//*[@class='notable-traits-tooltiptext']", 0)
 
 // Checking on very small mobile. The `i` should be on its own line.
 size: (365, 600)
@@ -126,3 +137,76 @@ compare-elements-position-false: (
     "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']",
     ("y", "x"),
 )
+
+// Now check the colors.
+define-function: (
+    "check-colors",
+    (theme, header_color, content_color, type_color, trait_color),
+    [
+        ("goto", "file://" + |DOC_PATH| + "/test_docs/struct.NotableStructWithLongName.html"),
+        // This is needed to ensure that the text color is computed.
+        ("show-text", true),
+
+        // Setting the theme.
+        ("local-storage", {"rustdoc-theme": |theme|, "rustdoc-use-system-theme": "false"}),
+        // We reload the page so the local storage settings are being used.
+        ("reload"),
+
+        ("move-cursor-to", "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']"),
+        ("assert-count", (".notable-traits-tooltiptext", 1)),
+
+        ("assert-css", (
+             ".notable-traits-tooltiptext h3.notable",
+             {"color": |header_color|},
+             ALL,
+        )),
+        ("assert-css", (
+             ".notable-traits-tooltiptext pre.content",
+             {"color": |content_color|},
+             ALL,
+        )),
+        ("assert-css", (
+             ".notable-traits-tooltiptext pre.content a.struct",
+             {"color": |type_color|},
+             ALL,
+        )),
+        ("assert-css", (
+             ".notable-traits-tooltiptext pre.content a.trait",
+             {"color": |trait_color|},
+             ALL,
+        )),
+    ]
+)
+
+call-function: (
+    "check-colors",
+    {
+        "theme": "ayu",
+        "content_color": "rgb(230, 225, 207)",
+        "header_color": "rgb(255, 255, 255)",
+        "type_color": "rgb(255, 160, 165)",
+        "trait_color": "rgb(57, 175, 215)",
+    },
+)
+
+call-function: (
+    "check-colors",
+    {
+        "theme": "dark",
+        "content_color": "rgb(221, 221, 221)",
+        "header_color": "rgb(221, 221, 221)",
+        "type_color": "rgb(45, 191, 184)",
+        "trait_color": "rgb(183, 140, 242)",
+    },
+)
+
+call-function: (
+    "check-colors",
+    {
+        "theme": "light",
+        "content_color": "rgb(0, 0, 0)",
+        "header_color": "rgb(0, 0, 0)",
+        "type_color": "rgb(173, 55, 138)",
+        "trait_color": "rgb(110, 79, 201)",
+    },
+)
diff --git a/src/test/rustdoc/doc-notable_trait-slice.bare_fn_matches.html b/src/test/rustdoc/doc-notable_trait-slice.bare_fn_matches.html
new file mode 100644
index 00000000000..6b58be7e685
--- /dev/null
+++ b/src/test/rustdoc/doc-notable_trait-slice.bare_fn_matches.html
@@ -0,0 +1 @@
+<script type="text/json" id="notable-traits-data">{"&amp;'static [SomeStruct]":"&lt;h3 class=\"notable\"&gt;Notable traits for &lt;code&gt;&amp;amp;[&lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait_slice::SomeStruct\"&gt;SomeStruct&lt;/a&gt;]&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait_slice::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &amp;amp;[&lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait_slice::SomeStruct\"&gt;SomeStruct&lt;/a&gt;]&lt;/span&gt;"}</script>
\ No newline at end of file
diff --git a/src/test/rustdoc/doc-notable_trait-slice.rs b/src/test/rustdoc/doc-notable_trait-slice.rs
index b0d41402721..2411da8cd45 100644
--- a/src/test/rustdoc/doc-notable_trait-slice.rs
+++ b/src/test/rustdoc/doc-notable_trait-slice.rs
@@ -8,13 +8,13 @@ pub struct OtherStruct;
 impl SomeTrait for &[SomeStruct] {}
 
 // @has doc_notable_trait_slice/fn.bare_fn_matches.html
-// @has - '//code[@class="content"]' 'impl SomeTrait for &[SomeStruct]'
+// @snapshot bare_fn_matches - '//script[@id="notable-traits-data"]'
 pub fn bare_fn_matches() -> &'static [SomeStruct] {
     &[]
 }
 
 // @has doc_notable_trait_slice/fn.bare_fn_no_matches.html
-// @!has - '//code[@class="content"]' 'impl SomeTrait for &[SomeStruct]'
+// @count - '//script[@id="notable-traits-data"]' 0
 pub fn bare_fn_no_matches() -> &'static [OtherStruct] {
     &[]
 }
diff --git a/src/test/rustdoc/doc-notable_trait.bare-fn.html b/src/test/rustdoc/doc-notable_trait.bare-fn.html
new file mode 100644
index 00000000000..4e4a3f18f24
--- /dev/null
+++ b/src/test/rustdoc/doc-notable_trait.bare-fn.html
@@ -0,0 +1 @@
+<script type="text/json" id="notable-traits-data">{"SomeStruct":"&lt;h3 class=\"notable\"&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait::SomeStruct\"&gt;SomeStruct&lt;/a&gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait::SomeStruct\"&gt;SomeStruct&lt;/a&gt;&lt;/span&gt;"}</script>
\ No newline at end of file
diff --git a/src/test/rustdoc/doc-notable_trait.rs b/src/test/rustdoc/doc-notable_trait.rs
index 58a24b855d6..1f2cd7181c3 100644
--- a/src/test/rustdoc/doc-notable_trait.rs
+++ b/src/test/rustdoc/doc-notable_trait.rs
@@ -9,7 +9,8 @@ impl<T: SomeTrait> SomeTrait for Wrapper<T> {}
 #[doc(notable_trait)]
 pub trait SomeTrait {
     // @has doc_notable_trait/trait.SomeTrait.html
-    // @has - '//code[@class="content"]' 'impl<T: SomeTrait> SomeTrait for Wrapper<T>'
+    // @has - '//span[@class="notable-traits"]/@data-ty' 'Wrapper<Self>'
+    // @snapshot wrap-me - '//script[@id="notable-traits-data"]'
     fn wrap_me(self) -> Wrapper<Self> where Self: Sized {
         Wrapper {
             inner: self,
@@ -22,15 +23,16 @@ impl SomeTrait for SomeStruct {}
 
 impl SomeStruct {
     // @has doc_notable_trait/struct.SomeStruct.html
-    // @has - '//code[@class="content"]' 'impl SomeTrait for SomeStruct'
-    // @has - '//code[@class="content"]' 'impl<T: SomeTrait> SomeTrait for Wrapper<T>'
+    // @has - '//span[@class="notable-traits"]/@data-ty' 'SomeStruct'
+    // @snapshot some-struct-new - '//script[@id="notable-traits-data"]'
     pub fn new() -> SomeStruct {
         SomeStruct
     }
 }
 
 // @has doc_notable_trait/fn.bare_fn.html
-// @has - '//code[@class="content"]' 'impl SomeTrait for SomeStruct'
+// @has - '//span[@class="notable-traits"]/@data-ty' 'SomeStruct'
+// @snapshot bare-fn - '//script[@id="notable-traits-data"]'
 pub fn bare_fn() -> SomeStruct {
     SomeStruct
 }
diff --git a/src/test/rustdoc/doc-notable_trait.some-struct-new.html b/src/test/rustdoc/doc-notable_trait.some-struct-new.html
new file mode 100644
index 00000000000..c0fd9748fd3
--- /dev/null
+++ b/src/test/rustdoc/doc-notable_trait.some-struct-new.html
@@ -0,0 +1 @@
+<script type="text/json" id="notable-traits-data">{"SomeStruct":"&lt;h3 class=\"notable\"&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait::SomeStruct\"&gt;SomeStruct&lt;/a&gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait::SomeStruct\"&gt;SomeStruct&lt;/a&gt;&lt;/span&gt;","Wrapper&lt;Self&gt;":"&lt;h3 class=\"notable\"&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct doc_notable_trait::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl&amp;lt;T:&amp;nbsp;&lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt;&amp;gt; &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct doc_notable_trait::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/span&gt;"}</script>
\ No newline at end of file
diff --git a/src/test/rustdoc/doc-notable_trait.wrap-me.html b/src/test/rustdoc/doc-notable_trait.wrap-me.html
new file mode 100644
index 00000000000..9a59d5edd12
--- /dev/null
+++ b/src/test/rustdoc/doc-notable_trait.wrap-me.html
@@ -0,0 +1 @@
+<script type="text/json" id="notable-traits-data">{"Wrapper&lt;Self&gt;":"&lt;h3 class=\"notable\"&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct doc_notable_trait::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl&amp;lt;T:&amp;nbsp;&lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt;&amp;gt; &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct doc_notable_trait::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/span&gt;"}</script>
\ No newline at end of file
diff --git a/src/test/rustdoc/spotlight-from-dependency.odd.html b/src/test/rustdoc/spotlight-from-dependency.odd.html
new file mode 100644
index 00000000000..987a949af44
--- /dev/null
+++ b/src/test/rustdoc/spotlight-from-dependency.odd.html
@@ -0,0 +1 @@
+<script type="text/json" id="notable-traits-data">{"Odd":"&lt;h3 class=\"notable\"&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.Odd.html\" title=\"struct foo::Odd\"&gt;Odd&lt;/a&gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl &lt;a class=\"trait\" href=\"{{channel}}/core/iter/traits/iterator/trait.Iterator.html\" title=\"trait core::iter::traits::iterator::Iterator\"&gt;Iterator&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.Odd.html\" title=\"struct foo::Odd\"&gt;Odd&lt;/a&gt;&lt;/span&gt;&lt;span class=\"where fmt-newline\"&gt;    type &lt;a href=\"{{channel}}/core/iter/traits/iterator/trait.Iterator.html#associatedtype.Item\" class=\"associatedtype\"&gt;Item&lt;/a&gt; = &lt;a class=\"primitive\" href=\"{{channel}}/std/primitive.usize.html\"&gt;usize&lt;/a&gt;;&lt;/span&gt;"}</script>
\ No newline at end of file
diff --git a/src/test/rustdoc/spotlight-from-dependency.rs b/src/test/rustdoc/spotlight-from-dependency.rs
index 5245789212d..156aedca62b 100644
--- a/src/test/rustdoc/spotlight-from-dependency.rs
+++ b/src/test/rustdoc/spotlight-from-dependency.rs
@@ -3,7 +3,8 @@
 use std::iter::Iterator;
 
 // @has foo/struct.Odd.html
-// @has - '//*[@id="method.new"]//span[@class="notable-traits"]//code/span' 'impl Iterator for Odd'
+// @has - '//*[@id="method.new"]//span[@class="notable-traits"]/@data-ty' 'Odd'
+// @snapshot odd - '//script[@id="notable-traits-data"]'
 pub struct Odd {
     current: usize,
 }