2018-01-23 00:45:34 +01:00
|
|
|
|
use std::path::PathBuf;
|
2013-09-18 22:18:38 -07:00
|
|
|
|
|
2020-12-31 23:25:30 -05:00
|
|
|
|
use rustc_data_structures::fx::FxHashMap;
|
|
|
|
|
|
2019-02-23 16:40:07 +09:00
|
|
|
|
use crate::externalfiles::ExternalHtml;
|
2019-10-10 12:09:01 +02:00
|
|
|
|
use crate::html::escape::Escape;
|
2019-08-31 09:07:29 -04:00
|
|
|
|
use crate::html::format::{Buffer, Print};
|
2020-07-12 14:37:22 -04:00
|
|
|
|
use crate::html::render::{ensure_trailing_slash, StylePath};
|
2019-01-31 15:42:45 +01:00
|
|
|
|
|
2015-01-03 22:54:18 -05:00
|
|
|
|
#[derive(Clone)]
|
2020-11-14 17:59:58 -05:00
|
|
|
|
crate struct Layout {
|
|
|
|
|
crate logo: String,
|
|
|
|
|
crate favicon: String,
|
|
|
|
|
crate external_html: ExternalHtml,
|
2020-12-31 23:25:30 -05:00
|
|
|
|
crate default_settings: FxHashMap<String, String>,
|
2020-11-14 17:59:58 -05:00
|
|
|
|
crate krate: String,
|
2019-08-30 10:51:13 -04:00
|
|
|
|
/// The given user css file which allow to customize the generated
|
|
|
|
|
/// documentation theme.
|
2020-11-14 17:59:58 -05:00
|
|
|
|
crate css_file_extension: Option<PathBuf>,
|
2019-08-30 10:51:13 -04:00
|
|
|
|
/// If false, the `select` element to have search filtering by crates on rendered docs
|
|
|
|
|
/// won't be generated.
|
2020-11-14 17:59:58 -05:00
|
|
|
|
crate generate_search_filter: bool,
|
2013-09-18 22:18:38 -07:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-14 17:59:58 -05:00
|
|
|
|
crate struct Page<'a> {
|
|
|
|
|
crate title: &'a str,
|
|
|
|
|
crate css_class: &'a str,
|
|
|
|
|
crate root_path: &'a str,
|
|
|
|
|
crate static_root_path: Option<&'a str>,
|
|
|
|
|
crate description: &'a str,
|
|
|
|
|
crate keywords: &'a str,
|
|
|
|
|
crate resource_suffix: &'a str,
|
|
|
|
|
crate extra_scripts: &'a [&'a str],
|
|
|
|
|
crate static_extra_scripts: &'a [&'a str],
|
2013-09-18 22:18:38 -07:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-14 17:59:58 -05:00
|
|
|
|
crate fn render<T: Print, S: Print>(
|
2018-12-20 13:28:55 +01:00
|
|
|
|
layout: &Layout,
|
2019-02-23 16:40:07 +09:00
|
|
|
|
page: &Page<'_>,
|
2019-08-31 11:22:51 -04:00
|
|
|
|
sidebar: S,
|
2019-08-31 12:18:25 -04:00
|
|
|
|
t: T,
|
2020-07-12 14:37:22 -04:00
|
|
|
|
style_files: &[StylePath],
|
2019-08-30 10:35:14 -04:00
|
|
|
|
) -> String {
|
2018-12-20 10:18:45 -06:00
|
|
|
|
let static_root_path = page.static_root_path.unwrap_or(page.root_path);
|
2019-12-22 17:42:04 -05:00
|
|
|
|
format!(
|
|
|
|
|
"<!DOCTYPE html>\
|
2018-04-14 17:13:46 +02:00
|
|
|
|
<html lang=\"en\">\
|
|
|
|
|
<head>\
|
|
|
|
|
<meta charset=\"utf-8\">\
|
|
|
|
|
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
|
|
|
|
|
<meta name=\"generator\" content=\"rustdoc\">\
|
|
|
|
|
<meta name=\"description\" content=\"{description}\">\
|
|
|
|
|
<meta name=\"keywords\" content=\"{keywords}\">\
|
|
|
|
|
<title>{title}</title>\
|
2018-12-20 10:18:45 -06:00
|
|
|
|
<link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}normalize{suffix}.css\">\
|
|
|
|
|
<link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}rustdoc{suffix}.css\" \
|
2018-04-14 17:13:46 +02:00
|
|
|
|
id=\"mainThemeStyle\">\
|
2020-07-12 14:37:22 -04:00
|
|
|
|
{style_files}\
|
2020-09-23 22:44:54 +01:00
|
|
|
|
<script id=\"default-settings\"{default_settings}></script>\
|
2018-12-20 10:18:45 -06:00
|
|
|
|
<script src=\"{static_root_path}storage{suffix}.js\"></script>\
|
2021-03-13 21:21:03 +01:00
|
|
|
|
<script src=\"{root_path}crates{suffix}.js\"></script>\
|
2018-12-20 10:18:45 -06:00
|
|
|
|
<noscript><link rel=\"stylesheet\" href=\"{static_root_path}noscript{suffix}.css\"></noscript>\
|
2018-04-14 17:13:46 +02:00
|
|
|
|
{css_extension}\
|
|
|
|
|
{favicon}\
|
|
|
|
|
{in_header}\
|
2018-11-29 01:29:49 +01:00
|
|
|
|
<style type=\"text/css\">\
|
2018-12-20 10:18:45 -06:00
|
|
|
|
#crate-search{{background-image:url(\"{static_root_path}down-arrow{suffix}.svg\");}}\
|
2018-11-29 01:29:49 +01:00
|
|
|
|
</style>\
|
2018-04-14 17:13:46 +02:00
|
|
|
|
</head>\
|
|
|
|
|
<body class=\"rustdoc {css_class}\">\
|
2021-04-19 11:40:25 -07:00
|
|
|
|
<!--[if lte IE 11]>\
|
2018-04-14 17:13:46 +02:00
|
|
|
|
<div class=\"warning\">\
|
|
|
|
|
This old browser is unsupported and will most likely display funky \
|
|
|
|
|
things.\
|
|
|
|
|
</div>\
|
|
|
|
|
<![endif]-->\
|
|
|
|
|
{before_content}\
|
|
|
|
|
<nav class=\"sidebar\">\
|
2021-02-27 12:37:05 +02:00
|
|
|
|
<div class=\"sidebar-menu\" role=\"button\">☰</div>\
|
2018-04-14 17:13:46 +02:00
|
|
|
|
{logo}\
|
|
|
|
|
{sidebar}\
|
|
|
|
|
</nav>\
|
|
|
|
|
<div class=\"theme-picker\">\
|
2020-10-30 13:35:41 -07:00
|
|
|
|
<button id=\"theme-picker\" aria-label=\"Pick another theme!\" aria-haspopup=\"menu\">\
|
2018-12-20 10:18:45 -06:00
|
|
|
|
<img src=\"{static_root_path}brush{suffix}.svg\" \
|
2021-02-19 17:54:41 -08:00
|
|
|
|
width=\"18\" height=\"18\" \
|
2018-12-20 10:18:45 -06:00
|
|
|
|
alt=\"Pick another theme!\">\
|
2018-04-14 17:13:46 +02:00
|
|
|
|
</button>\
|
2020-10-30 13:35:41 -07:00
|
|
|
|
<div id=\"theme-choices\" role=\"menu\"></div>\
|
2018-04-14 17:13:46 +02:00
|
|
|
|
</div>\
|
|
|
|
|
<nav class=\"sub\">\
|
2019-11-11 16:54:45 +01:00
|
|
|
|
<form class=\"search-form\">\
|
2018-04-14 17:13:46 +02:00
|
|
|
|
<div class=\"search-container\">\
|
2018-12-20 13:28:55 +01:00
|
|
|
|
<div>{filter_crates}\
|
2018-10-01 00:47:54 +02:00
|
|
|
|
<input class=\"search-input\" name=\"search\" \
|
2019-11-11 16:54:45 +01:00
|
|
|
|
disabled \
|
2018-10-01 00:47:54 +02:00
|
|
|
|
autocomplete=\"off\" \
|
|
|
|
|
spellcheck=\"false\" \
|
|
|
|
|
placeholder=\"Click or press ‘S’ to search, ‘?’ for more options…\" \
|
|
|
|
|
type=\"search\">\
|
|
|
|
|
</div>\
|
2020-12-12 23:30:13 +01:00
|
|
|
|
<button type=\"button\" class=\"help-button\">?</button>
|
2018-04-13 22:54:09 +02:00
|
|
|
|
<a id=\"settings-menu\" href=\"{root_path}settings.html\">\
|
2018-12-20 10:18:45 -06:00
|
|
|
|
<img src=\"{static_root_path}wheel{suffix}.svg\" \
|
2021-02-19 17:54:41 -08:00
|
|
|
|
width=\"18\" height=\"18\" \
|
2018-12-20 10:18:45 -06:00
|
|
|
|
alt=\"Change settings\">\
|
2018-04-13 22:54:09 +02:00
|
|
|
|
</a>\
|
2018-04-14 17:13:46 +02:00
|
|
|
|
</div>\
|
|
|
|
|
</form>\
|
|
|
|
|
</nav>\
|
|
|
|
|
<section id=\"main\" class=\"content\">{content}</section>\
|
|
|
|
|
<section id=\"search\" class=\"content hidden\"></section>\
|
|
|
|
|
{after_content}\
|
Load rustdoc's JS search index on-demand.
Instead of being loaded on every page, the JS search index is now
loaded when either (a) there is a `?search=` param, or (b) the search
input is focused.
This saves both CPU and bandwidth. As of Feb 2021,
https://doc.rust-lang.org/search-index1.50.0.js is 273,838 bytes
gzipped or 2,544,939 bytes uncompressed. Evaluating it takes 445 ms
of CPU time in Chrome 88 on a i7-10710U CPU (out of a total ~2,100
ms page reload).
Generate separate JS file with crate names.
This is much smaller than the full search index, and is used in the "hot
path" to draw the page. In particular it's used to crate the dropdown
for the search bar, and to append a list of crates to the sidebar (on
some pages).
Skip early search that can bypass 500ms timeout.
This was occurring when someone had typed some text during the load of
search-index.js. Their query was usually not ready to execute, and the
search itself is fairly expensive, delaying the overall load, which
delayed the input / keyup events, which delayed eventually executing the
query.
2021-02-19 17:22:30 -08:00
|
|
|
|
<div id=\"rustdoc-vars\" data-root-path=\"{root_path}\" data-current-crate=\"{krate}\" \
|
2021-04-13 14:59:54 -07:00
|
|
|
|
data-search-index-js=\"{root_path}search-index{suffix}.js\" \
|
2021-04-14 11:56:53 -07:00
|
|
|
|
data-search-js=\"{static_root_path}search{suffix}.js\"></div>
|
2018-12-20 10:18:45 -06:00
|
|
|
|
<script src=\"{static_root_path}main{suffix}.js\"></script>\
|
2018-11-06 01:40:12 +01:00
|
|
|
|
{extra_scripts}\
|
2018-04-14 17:13:46 +02:00
|
|
|
|
</body>\
|
|
|
|
|
</html>",
|
2019-12-22 17:42:04 -05:00
|
|
|
|
css_extension = if layout.css_file_extension.is_some() {
|
|
|
|
|
format!(
|
|
|
|
|
"<link rel=\"stylesheet\" \
|
2018-12-20 10:18:45 -06:00
|
|
|
|
type=\"text/css\" \
|
|
|
|
|
href=\"{static_root_path}theme{suffix}.css\">",
|
|
|
|
|
static_root_path = static_root_path,
|
2019-12-22 17:42:04 -05:00
|
|
|
|
suffix = page.resource_suffix
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
String::new()
|
|
|
|
|
},
|
|
|
|
|
content = Buffer::html().to_display(t),
|
|
|
|
|
static_root_path = static_root_path,
|
|
|
|
|
root_path = page.root_path,
|
|
|
|
|
css_class = page.css_class,
|
|
|
|
|
logo = {
|
|
|
|
|
if layout.logo.is_empty() {
|
|
|
|
|
format!(
|
2021-01-30 01:02:18 +00:00
|
|
|
|
"<a href='{root}{path}index.html'>\
|
2020-08-07 11:19:07 +02:00
|
|
|
|
<div class='logo-container rust-logo'>\
|
2019-04-29 11:45:06 +02:00
|
|
|
|
<img src='{static_root_path}rust-logo{suffix}.png' alt='logo'></div></a>",
|
2021-01-30 01:02:18 +00:00
|
|
|
|
root = page.root_path,
|
|
|
|
|
path = ensure_trailing_slash(&layout.krate),
|
2019-12-22 17:42:04 -05:00
|
|
|
|
static_root_path = static_root_path,
|
|
|
|
|
suffix = page.resource_suffix
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
format!(
|
2021-01-30 01:02:18 +00:00
|
|
|
|
"<a href='{root}{path}index.html'>\
|
|
|
|
|
<div class='logo-container'><img src='{logo}' alt='logo'></div></a>",
|
|
|
|
|
root = page.root_path,
|
|
|
|
|
path = ensure_trailing_slash(&layout.krate),
|
|
|
|
|
logo = layout.logo
|
2019-12-22 17:42:04 -05:00
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
title = page.title,
|
|
|
|
|
description = page.description,
|
|
|
|
|
keywords = page.keywords,
|
|
|
|
|
favicon = if layout.favicon.is_empty() {
|
|
|
|
|
format!(
|
2020-09-23 21:31:27 -04:00
|
|
|
|
r##"<link rel="icon" type="image/svg+xml" href="{static_root_path}favicon{suffix}.svg">
|
|
|
|
|
<link rel="alternate icon" type="image/png" href="{static_root_path}favicon-16x16{suffix}.png">
|
|
|
|
|
<link rel="alternate icon" type="image/png" href="{static_root_path}favicon-32x32{suffix}.png">"##,
|
2019-12-22 17:42:04 -05:00
|
|
|
|
static_root_path = static_root_path,
|
|
|
|
|
suffix = page.resource_suffix
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
format!(r#"<link rel="shortcut icon" href="{}">"#, layout.favicon)
|
|
|
|
|
},
|
|
|
|
|
in_header = layout.external_html.in_header,
|
|
|
|
|
before_content = layout.external_html.before_content,
|
|
|
|
|
after_content = layout.external_html.after_content,
|
|
|
|
|
sidebar = Buffer::html().to_display(sidebar),
|
|
|
|
|
krate = layout.krate,
|
2020-09-23 22:44:54 +01:00
|
|
|
|
default_settings = layout
|
|
|
|
|
.default_settings
|
|
|
|
|
.iter()
|
2020-10-28 20:12:15 +00:00
|
|
|
|
.map(|(k, v)| format!(r#" data-{}="{}""#, k.replace('-', "_"), Escape(v)))
|
2020-09-23 22:44:54 +01:00
|
|
|
|
.collect::<String>(),
|
2020-07-12 14:37:22 -04:00
|
|
|
|
style_files = style_files
|
2019-12-22 17:42:04 -05:00
|
|
|
|
.iter()
|
2020-07-12 14:37:22 -04:00
|
|
|
|
.filter_map(|t| {
|
|
|
|
|
if let Some(stem) = t.path.file_stem() { Some((stem, t.disabled)) } else { None }
|
|
|
|
|
})
|
|
|
|
|
.filter_map(|t| {
|
|
|
|
|
if let Some(path) = t.0.to_str() { Some((path, t.1)) } else { None }
|
|
|
|
|
})
|
2019-12-22 17:42:04 -05:00
|
|
|
|
.map(|t| format!(
|
2020-07-12 14:37:22 -04:00
|
|
|
|
r#"<link rel="stylesheet" type="text/css" href="{}.css" {} {}>"#,
|
|
|
|
|
Escape(&format!("{}{}{}", static_root_path, t.0, page.resource_suffix)),
|
|
|
|
|
if t.1 { "disabled" } else { "" },
|
|
|
|
|
if t.0 == "light" { "id=\"themeStyle\"" } else { "" }
|
2019-12-22 17:42:04 -05:00
|
|
|
|
))
|
|
|
|
|
.collect::<String>(),
|
|
|
|
|
suffix = page.resource_suffix,
|
2021-01-30 01:02:18 +00:00
|
|
|
|
extra_scripts = page
|
2019-12-22 17:42:04 -05:00
|
|
|
|
.static_extra_scripts
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|e| {
|
|
|
|
|
format!(
|
|
|
|
|
"<script src=\"{static_root_path}{extra_script}.js\"></script>",
|
|
|
|
|
static_root_path = static_root_path,
|
|
|
|
|
extra_script = e
|
|
|
|
|
)
|
|
|
|
|
})
|
2021-01-30 01:02:18 +00:00
|
|
|
|
.chain(page.extra_scripts.iter().map(|e| {
|
2019-12-22 17:42:04 -05:00
|
|
|
|
format!(
|
|
|
|
|
"<script src=\"{root_path}{extra_script}.js\"></script>",
|
|
|
|
|
root_path = page.root_path,
|
|
|
|
|
extra_script = e
|
|
|
|
|
)
|
2021-01-30 01:02:18 +00:00
|
|
|
|
}))
|
2019-12-22 17:42:04 -05:00
|
|
|
|
.collect::<String>(),
|
|
|
|
|
filter_crates = if layout.generate_search_filter {
|
|
|
|
|
"<select id=\"crate-search\">\
|
2020-08-31 13:16:50 +02:00
|
|
|
|
<option value=\"All crates\">All crates</option>\
|
|
|
|
|
</select>"
|
2019-12-22 17:42:04 -05:00
|
|
|
|
} else {
|
|
|
|
|
""
|
|
|
|
|
},
|
2019-08-31 08:52:04 -04:00
|
|
|
|
)
|
2013-09-18 22:18:38 -07:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-14 17:59:58 -05:00
|
|
|
|
crate fn redirect(url: &str) -> String {
|
2014-12-04 12:48:16 +09:00
|
|
|
|
// <script> triggers a redirect before refresh, so this is fine.
|
2019-08-30 10:35:14 -04:00
|
|
|
|
format!(
|
2019-12-22 17:42:04 -05:00
|
|
|
|
r##"<!DOCTYPE html>
|
2014-05-29 13:50:47 -07:00
|
|
|
|
<html lang="en">
|
|
|
|
|
<head>
|
|
|
|
|
<meta http-equiv="refresh" content="0;URL={url}">
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
2014-12-04 12:48:16 +09:00
|
|
|
|
<p>Redirecting to <a href="{url}">{url}</a>...</p>
|
|
|
|
|
<script>location.replace("{url}" + location.search + location.hash);</script>
|
2014-05-29 13:50:47 -07:00
|
|
|
|
</body>
|
|
|
|
|
</html>"##,
|
2019-12-22 17:42:04 -05:00
|
|
|
|
url = url,
|
2014-05-29 13:50:47 -07:00
|
|
|
|
)
|
|
|
|
|
}
|