Skip to content

Commit d84e02e

Browse files
committed
rustdoc: use browser-native tooltips for code and notable hover
This is the simplest way to adopt the timing guidelines described in <https://www.nngroup.com/articles/timing-exposing-content/>. It replaces the mouseover interactions used for "tooltip" popovers with HTML `title` attributes, which already do the right thing. The JavaScript-based popover code remains for click interaction, so that these elements remain accessible on touch screens, and because it's not possible to put links in native tooltips.
1 parent 8b4b208 commit d84e02e

10 files changed

+71
-68
lines changed

src/librustdoc/html/render/mod.rs

+36-7
Original file line numberDiff line numberDiff line change
@@ -1279,8 +1279,6 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) ->
12791279
}
12801280

12811281
pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> Option<String> {
1282-
let mut has_notable_trait = false;
1283-
12841282
let did = ty.def_id(cx.cache())?;
12851283

12861284
// Box has pass-through impls for Read, Write, Iterator, and Future when the
@@ -1293,6 +1291,8 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> O
12931291
return None;
12941292
}
12951293

1294+
let mut notable_tt = String::new();
1295+
12961296
if let Some(impls) = cx.cache().impls.get(&did) {
12971297
for i in impls {
12981298
let impl_ = i.inner_impl();
@@ -1306,20 +1306,49 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> O
13061306

13071307
if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable_trait(cx.tcx()))
13081308
{
1309-
has_notable_trait = true;
1309+
if !notable_tt.is_empty() {
1310+
notable_tt.push('\n');
1311+
}
1312+
write!(&mut notable_tt, " {:#}", impl_.print(false, cx))
1313+
.expect("infallible write");
1314+
for it in &impl_.items {
1315+
if let clean::AssocTypeItem(ref tydef, ref bounds) = *it.kind {
1316+
write!(
1317+
&mut notable_tt,
1318+
"\n type {name}{generics:#}",
1319+
name = it.name.as_ref().unwrap(),
1320+
generics = tydef.generics.print(cx),
1321+
)
1322+
.expect("infallible write");
1323+
if !bounds.is_empty() {
1324+
write!(&mut notable_tt, ": {:#}", print_generic_bounds(bounds, cx))
1325+
.expect("infallible write");
1326+
}
1327+
write!(
1328+
&mut notable_tt,
1329+
"{:#}",
1330+
print_where_clause(&tydef.generics, cx, 4, Ending::NoNewline)
1331+
)
1332+
.expect("infallible write");
1333+
write!(&mut notable_tt, " = {:#}", tydef.type_.print(cx))
1334+
.expect("infallible write");
1335+
}
1336+
}
13101337
}
13111338
}
13121339
}
13131340
}
13141341

1315-
if has_notable_trait {
1342+
if notable_tt.is_empty() {
1343+
None
1344+
} else {
13161345
cx.types_with_notable_traits.insert(ty.clone());
13171346
Some(format!(
1318-
" <a href=\"#\" class=\"tooltip\" data-notable-ty=\"{ty}\">ⓘ</a>",
1347+
" <a href=\"#\" title=\"Notable traits for {ty}&#10;{tt}\" class=\"tooltip\" \
1348+
data-notable-ty=\"{ty}\">ⓘ</a>",
13191349
ty = Escape(&format!("{:#}", ty.print(cx))),
1350+
tt = Escape(&notable_tt),
13201351
))
1321-
} else {
1322-
None
13231352
}
13241353
}
13251354

src/librustdoc/html/static/css/noscript.css

-6
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,3 @@ nav.sub {
2222
.source .sidebar {
2323
display: none;
2424
}
25-
26-
.notable-traits {
27-
/* layout requires javascript
28-
https://github.com/rust-lang/rust/issues/102576 */
29-
display: none;
30-
}

src/librustdoc/html/static/css/rustdoc.css

-12
Original file line numberDiff line numberDiff line change
@@ -1179,18 +1179,6 @@ a.test-arrow:hover {
11791179
position: relative;
11801180
}
11811181

1182-
/* placeholder thunk so that the mouse can easily travel from "(i)" to popover
1183-
the resulting "hover tunnel" is a stepped triangle, approximating
1184-
https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown */
1185-
a.tooltip:hover::after {
1186-
position: absolute;
1187-
top: calc(100% - 10px);
1188-
left: -15px;
1189-
right: -15px;
1190-
height: 20px;
1191-
content: "\00a0";
1192-
}
1193-
11941182
.popover.tooltip .content {
11951183
margin: 0.25em 0.5em;
11961184
}

src/librustdoc/html/static/js/main.js

+4-38
Original file line numberDiff line numberDiff line change
@@ -740,12 +740,8 @@ function preLoadCss(cssUrl) {
740740
//
741741
// This means when the window is resized, we need to redo the layout.
742742
const base = window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE;
743-
const force_visible = base.TOOLTIP_FORCE_VISIBLE;
744743
hideTooltip(false);
745-
if (force_visible) {
746-
showTooltip(base);
747-
base.TOOLTIP_FORCE_VISIBLE = true;
748-
}
744+
showTooltip(base);
749745
}
750746
});
751747

@@ -824,15 +820,6 @@ function preLoadCss(cssUrl) {
824820
wrapper.style.visibility = "";
825821
window.CURRENT_TOOLTIP_ELEMENT = wrapper;
826822
window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE = e;
827-
wrapper.onpointerleave = function(ev) {
828-
// If this is a synthetic touch event, ignore it. A click event will be along shortly.
829-
if (ev.pointerType !== "mouse") {
830-
return;
831-
}
832-
if (!e.TOOLTIP_FORCE_VISIBLE && !elemIsInParent(event.relatedTarget, e)) {
833-
hideTooltip(true);
834-
}
835-
};
836823
}
837824

838825
function tooltipBlurHandler(event) {
@@ -856,11 +843,8 @@ function preLoadCss(cssUrl) {
856843

857844
function hideTooltip(focus) {
858845
if (window.CURRENT_TOOLTIP_ELEMENT) {
859-
if (window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE) {
860-
if (focus) {
861-
window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.focus();
862-
}
863-
window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE = false;
846+
if (focus) {
847+
window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.focus();
864848
}
865849
const body = document.getElementsByTagName("body")[0];
866850
body.removeChild(window.CURRENT_TOOLTIP_ELEMENT);
@@ -870,8 +854,7 @@ function preLoadCss(cssUrl) {
870854

871855
onEachLazy(document.getElementsByClassName("tooltip"), e => {
872856
e.onclick = function() {
873-
this.TOOLTIP_FORCE_VISIBLE = this.TOOLTIP_FORCE_VISIBLE ? false : true;
874-
if (window.CURRENT_TOOLTIP_ELEMENT && !this.TOOLTIP_FORCE_VISIBLE) {
857+
if (window.CURRENT_TOOLTIP_ELEMENT) {
875858
hideTooltip(true);
876859
} else {
877860
showTooltip(this);
@@ -881,23 +864,6 @@ function preLoadCss(cssUrl) {
881864
}
882865
return false;
883866
};
884-
e.onpointerenter = function(ev) {
885-
// If this is a synthetic touch event, ignore it. A click event will be along shortly.
886-
if (ev.pointerType !== "mouse") {
887-
return;
888-
}
889-
showTooltip(this);
890-
};
891-
e.onpointerleave = function(ev) {
892-
// If this is a synthetic touch event, ignore it. A click event will be along shortly.
893-
if (ev.pointerType !== "mouse") {
894-
return;
895-
}
896-
if (!this.TOOLTIP_FORCE_VISIBLE &&
897-
!elemIsInParent(ev.relatedTarget, window.CURRENT_TOOLTIP_ELEMENT)) {
898-
hideTooltip(true);
899-
}
900-
};
901867
});
902868

903869
const sidebar_menu_toggle = document.getElementsByClassName("sidebar-menu-toggle")[0];

tests/rustdoc-gui/notable-trait.goml

+1-5
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ compare-elements-position-false: (
4040
("x")
4141
)
4242
click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']"
43-
move-cursor-to: "//h1"
4443
assert-count: ("//*[@class='tooltip popover']", 0)
4544

4645
// Now only the `i` should be on the next line.
@@ -116,7 +115,6 @@ assert-position: (
116115
{"x": 0}
117116
)
118117
click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']"
119-
move-cursor-to: "//h1"
120118
assert-count: ("//*[@class='tooltip popover']", 0)
121119

122120
// Now check the colors.
@@ -133,7 +131,7 @@ define-function: (
133131
// We reload the page so the local storage settings are being used.
134132
reload:
135133

136-
move-cursor-to: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']"
134+
click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']"
137135
assert-count: (".tooltip.popover", 1)
138136

139137
assert-css: (
@@ -196,7 +194,6 @@ reload:
196194

197195
// Check that pressing escape works
198196
click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']"
199-
move-cursor-to: "//*[@class='tooltip popover']"
200197
assert-count: ("//*[@class='tooltip popover']", 1)
201198
press-key: "Escape"
202199
assert-count: ("//*[@class='tooltip popover']", 0)
@@ -211,7 +208,6 @@ assert-false: "#method\.create_an_iterator_from_read .tooltip:focus"
211208

212209
// Check that pressing tab over and over works.
213210
click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']"
214-
move-cursor-to: "//*[@class='tooltip popover']"
215211
assert-count: ("//*[@class='tooltip popover']", 1)
216212
press-key: "Tab"
217213
press-key: "Tab"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script type="text/json" id="notable-traits-data">{"Wrapper&lt;()&gt;":"&lt;h3&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct foo::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl&amp;lt;T&amp;gt; &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait foo::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct foo::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/span&gt;&lt;span class=\"where fmt-newline\"&gt; type &lt;a href=\"trait.SomeTrait.html#associatedtype.SomeType\" class=\"associatedtype\"&gt;SomeType&lt;/a&gt; = T;&lt;/span&gt;"}</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<a href="#" title="Notable traits for Wrapper&lt;()&gt;&#10; impl&lt;T&gt; SomeTrait for Wrapper&lt;T&gt;&#10; type SomeType = T" class="tooltip" data-notable-ty="Wrapper&lt;()&gt;">&#9432;</a>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#![feature(doc_notable_trait)]
2+
#![crate_name="foo"]
3+
4+
pub struct Wrapper<T> {
5+
inner: T,
6+
}
7+
8+
impl<T> SomeTrait for Wrapper<T> {
9+
type SomeType = T;
10+
}
11+
12+
#[doc(notable_trait)]
13+
pub trait SomeTrait {
14+
type SomeType;
15+
}
16+
17+
// @has foo/fn.bare_fn.html
18+
// @has - '//a[@class="tooltip"]/@data-notable-ty' 'Wrapper<()>'
19+
// @has - '//a[@class="tooltip"]/@title' 'impl<T> SomeTrait for Wrapper<T> type SomeType = T'
20+
// @snapshot bare_fn_tooltip - '//a[@class="tooltip"]'
21+
// @snapshot bare_fn_data - '//script[@id="notable-traits-data"]'
22+
pub fn bare_fn() -> Wrapper<()> {
23+
unimplemented();
24+
}

tests/rustdoc/notable-trait/doc-notable_trait-slice.rs

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub struct OtherStruct;
88
impl SomeTrait for &[SomeStruct] {}
99

1010
// @has doc_notable_trait_slice/fn.bare_fn_matches.html
11+
// @has - '//a[@class="tooltip"]/@title' 'impl SomeTrait for &[SomeStruct]'
1112
// @snapshot bare_fn_matches - '//script[@id="notable-traits-data"]'
1213
pub fn bare_fn_matches() -> &'static [SomeStruct] {
1314
&[]

tests/rustdoc/notable-trait/doc-notable_trait.rs

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ impl<T: SomeTrait> SomeTrait for Wrapper<T> {}
1010
pub trait SomeTrait {
1111
// @has doc_notable_trait/trait.SomeTrait.html
1212
// @has - '//a[@class="tooltip"]/@data-notable-ty' 'Wrapper<Self>'
13+
// @has - '//a[@class="tooltip"]/@title' 'impl<T: SomeTrait> SomeTrait for Wrapper<T>'
1314
// @snapshot wrap-me - '//script[@id="notable-traits-data"]'
1415
fn wrap_me(self) -> Wrapper<Self> where Self: Sized {
1516
Wrapper {
@@ -24,6 +25,7 @@ impl SomeTrait for SomeStruct {}
2425
impl SomeStruct {
2526
// @has doc_notable_trait/struct.SomeStruct.html
2627
// @has - '//a[@class="tooltip"]/@data-notable-ty' 'SomeStruct'
28+
// @has - '//a[@class="tooltip"]/@title' 'impl SomeTrait for SomeStruct'
2729
// @snapshot some-struct-new - '//script[@id="notable-traits-data"]'
2830
pub fn new() -> SomeStruct {
2931
SomeStruct
@@ -32,6 +34,7 @@ impl SomeStruct {
3234

3335
// @has doc_notable_trait/fn.bare_fn.html
3436
// @has - '//a[@class="tooltip"]/@data-notable-ty' 'SomeStruct'
37+
// @has - '//a[@class="tooltip"]/@title' 'impl SomeTrait for SomeStruct'
3538
// @snapshot bare-fn - '//script[@id="notable-traits-data"]'
3639
pub fn bare_fn() -> SomeStruct {
3740
SomeStruct

0 commit comments

Comments
 (0)