Skip to content

Commit 768d5e9

Browse files
committed
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.
1 parent 94736c4 commit 768d5e9

File tree

3 files changed

+69
-51
lines changed

3 files changed

+69
-51
lines changed

src/librustdoc/html/layout.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ crate fn render<T: Print, S: Print>(
5858
{style_files}\
5959
<script id=\"default-settings\"{default_settings}></script>\
6060
<script src=\"{static_root_path}storage{suffix}.js\"></script>\
61+
<script src=\"{static_root_path}crates{suffix}.js\"></script>\
6162
<noscript><link rel=\"stylesheet\" href=\"{static_root_path}noscript{suffix}.css\"></noscript>\
6263
{css_extension}\
6364
{favicon}\
@@ -112,10 +113,10 @@ crate fn render<T: Print, S: Print>(
112113
<section id=\"search\" class=\"content hidden\"></section>\
113114
<section class=\"footer\"></section>\
114115
{after_content}\
115-
<div id=\"rustdoc-vars\" data-root-path=\"{root_path}\" data-current-crate=\"{krate}\"></div>
116+
<div id=\"rustdoc-vars\" data-root-path=\"{root_path}\" data-current-crate=\"{krate}\" \
117+
data-search-js=\"{root_path}search-index{suffix}.js\"></div>
116118
<script src=\"{static_root_path}main{suffix}.js\"></script>\
117119
{extra_scripts}\
118-
<script defer src=\"{root_path}search-index{suffix}.js\"></script>\
119120
</body>\
120121
</html>",
121122
css_extension = if layout.css_file_extension.is_some() {

src/librustdoc/html/render/mod.rs

+10-7
Original file line numberDiff line numberDiff line change
@@ -1039,22 +1039,28 @@ themePicker.onblur = handleThemeButtonsBlur;
10391039
cx.shared.fs.write(&dst, v.as_bytes())?;
10401040
}
10411041

1042-
// Update the search index
1042+
// Update the search index and crate list.
10431043
let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix));
10441044
let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name.as_str()), &dst);
10451045
all_indexes.push(search_index);
1046+
krates.push(krate.name.to_string());
1047+
krates.sort();
10461048

10471049
// Sort the indexes by crate so the file will be generated identically even
10481050
// with rustdoc running in parallel.
10491051
all_indexes.sort();
10501052
{
10511053
let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
10521054
v.push_str(&all_indexes.join(",\\\n"));
1053-
// "addSearchOptions" has to be called first so the crate filtering can be set before the
1054-
// search might start (if it's set into the URL for example).
1055-
v.push_str("\\\n}');\naddSearchOptions(searchIndex);initSearch(searchIndex);");
1055+
v.push_str("\\\n}');\ninitSearch(searchIndex);");
10561056
cx.shared.fs.write(&dst, &v)?;
10571057
}
1058+
1059+
let crate_list_dst = cx.dst.join(&format!("crates{}.js", cx.shared.resource_suffix));
1060+
let crate_list =
1061+
format!("window.ALL_CRATES = [{}];", krates.iter().map(|k| format!("\"{}\"", k)).join(","));
1062+
cx.shared.fs.write(&crate_list_dst, &crate_list)?;
1063+
10581064
if options.enable_index_page {
10591065
if let Some(index_page) = options.index_page.clone() {
10601066
let mut md_opts = options.clone();
@@ -1076,9 +1082,6 @@ themePicker.onblur = handleThemeButtonsBlur;
10761082
extra_scripts: &[],
10771083
static_extra_scripts: &[],
10781084
};
1079-
krates.push(krate.name.to_string());
1080-
krates.sort();
1081-
krates.dedup();
10821085

10831086
let content = format!(
10841087
"<h1 class=\"fqn\">\

src/librustdoc/html/static/main.js

+56-42
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ if (!DOMTokenList.prototype.remove) {
4242
if (rustdocVars) {
4343
window.rootPath = rustdocVars.attributes["data-root-path"].value;
4444
window.currentCrate = rustdocVars.attributes["data-current-crate"].value;
45+
window.searchJS = rustdocVars.attributes["data-search-js"].value;
4546
}
4647
var sidebarVars = document.getElementById("sidebar-vars");
4748
if (sidebarVars) {
@@ -1922,8 +1923,8 @@ function defocusSearchBar() {
19221923
return searchWords;
19231924
}
19241925

1925-
function startSearch() {
1926-
var callback = function() {
1926+
function registerSearchEvents() {
1927+
var searchAfter500ms = function() {
19271928
clearInputTimeout();
19281929
if (search_input.value.length === 0) {
19291930
if (browserSupportsHistoryApi()) {
@@ -1935,8 +1936,8 @@ function defocusSearchBar() {
19351936
searchTimeout = setTimeout(search, 500);
19361937
}
19371938
};
1938-
search_input.onkeyup = callback;
1939-
search_input.oninput = callback;
1939+
search_input.onkeyup = searchAfter500ms;
1940+
search_input.oninput = searchAfter500ms;
19401941
document.getElementsByClassName("search-form")[0].onsubmit = function(e) {
19411942
e.preventDefault();
19421943
clearInputTimeout();
@@ -1999,7 +2000,6 @@ function defocusSearchBar() {
19992000
}
20002001
});
20012002
}
2002-
search();
20032003

20042004
// This is required in firefox to avoid this problem: Navigating to a search result
20052005
// with the keyboard, hitting enter, and then hitting back would take you back to
@@ -2017,8 +2017,14 @@ function defocusSearchBar() {
20172017
}
20182018

20192019
index = buildIndex(rawSearchIndex);
2020-
startSearch();
2020+
registerSearchEvents();
2021+
// If there's a search term in the URL, execute the search now.
2022+
if (getQueryStringParams().search) {
2023+
search();
2024+
}
2025+
};
20212026

2027+
function addSidebarCrates(crates) {
20222028
// Draw a convenient sidebar of known crates if we have a listing
20232029
if (window.rootPath === "../" || window.rootPath === "./") {
20242030
var sidebar = document.getElementsByClassName("sidebar-elems")[0];
@@ -2029,24 +2035,13 @@ function defocusSearchBar() {
20292035
var ul = document.createElement("ul");
20302036
div.appendChild(ul);
20312037

2032-
var crates = [];
2033-
for (var crate in rawSearchIndex) {
2034-
if (!hasOwnProperty(rawSearchIndex, crate)) {
2035-
continue;
2036-
}
2037-
crates.push(crate);
2038-
}
2039-
crates.sort();
20402038
for (var i = 0; i < crates.length; ++i) {
20412039
var klass = "crate";
20422040
if (window.rootPath !== "./" && crates[i] === window.currentCrate) {
20432041
klass += " current";
20442042
}
20452043
var link = document.createElement("a");
20462044
link.href = window.rootPath + crates[i] + "/index.html";
2047-
// The summary in the search index has HTML, so we need to
2048-
// dynamically render it as plaintext.
2049-
link.title = convertHTMLToPlaintext(rawSearchIndex[crates[i]].doc);
20502045
link.className = klass;
20512046
link.textContent = crates[i];
20522047

@@ -2057,7 +2052,7 @@ function defocusSearchBar() {
20572052
sidebar.appendChild(div);
20582053
}
20592054
}
2060-
};
2055+
}
20612056

20622057
/**
20632058
* Convert HTML to plaintext:
@@ -2862,45 +2857,26 @@ function defocusSearchBar() {
28622857
}
28632858
}
28642859

2865-
window.addSearchOptions = function(crates) {
2860+
function addSearchOptions(crates) {
28662861
var elem = document.getElementById("crate-search");
28672862

28682863
if (!elem) {
28692864
enableSearchInput();
28702865
return;
28712866
}
2872-
var crates_text = [];
2873-
if (Object.keys(crates).length > 1) {
2874-
for (var crate in crates) {
2875-
if (hasOwnProperty(crates, crate)) {
2876-
crates_text.push(crate);
2877-
}
2878-
}
2879-
}
2880-
crates_text.sort(function(a, b) {
2881-
var lower_a = a.toLowerCase();
2882-
var lower_b = b.toLowerCase();
2883-
2884-
if (lower_a < lower_b) {
2885-
return -1;
2886-
} else if (lower_a > lower_b) {
2887-
return 1;
2888-
}
2889-
return 0;
2890-
});
28912867
var savedCrate = getSettingValue("saved-filter-crate");
2892-
for (var i = 0, len = crates_text.length; i < len; ++i) {
2868+
for (var i = 0, len = crates.length; i < len; ++i) {
28932869
var option = document.createElement("option");
2894-
option.value = crates_text[i];
2895-
option.innerText = crates_text[i];
2870+
option.value = crates[i];
2871+
option.innerText = crates[i];
28962872
elem.appendChild(option);
28972873
// Set the crate filter from saved storage, if the current page has the saved crate
28982874
// filter.
28992875
//
29002876
// If not, ignore the crate filter -- we want to support filtering for crates on sites
29012877
// like doc.rust-lang.org where the crates may differ from page to page while on the
29022878
// same domain.
2903-
if (crates_text[i] === savedCrate) {
2879+
if (crates[i] === savedCrate) {
29042880
elem.value = savedCrate;
29052881
}
29062882
}
@@ -2969,6 +2945,44 @@ function defocusSearchBar() {
29692945
buildHelperPopup = function() {};
29702946
}
29712947

2948+
function loadScript(url) {
2949+
var script = document.createElement('script');
2950+
script.src = url;
2951+
document.head.append(script);
2952+
}
2953+
2954+
function setupSearchLoader() {
2955+
var searchLoaded = false;
2956+
function loadSearch() {
2957+
if (!searchLoaded) {
2958+
searchLoaded = true;
2959+
loadScript(window.searchJS);
2960+
}
2961+
}
2962+
2963+
// `crates{version}.js` should always be loaded before this script, so we can use it safely.
2964+
addSearchOptions(window.ALL_CRATES);
2965+
addSidebarCrates(window.ALL_CRATES);
2966+
2967+
search_input.addEventListener("focus", function() {
2968+
search_input.origPlaceholder = search_input.placeholder;
2969+
search_input.placeholder = "Type your search here.";
2970+
loadSearch();
2971+
});
2972+
search_input.addEventListener("blur", function() {
2973+
search_input.placeholder = search_input.origPlaceholder;
2974+
});
2975+
enableSearchInput();
2976+
2977+
var crateSearchDropDown = document.getElementById("crate-search");
2978+
crateSearchDropDown.addEventListener("focus", loadSearch);
2979+
var params = getQueryStringParams();
2980+
if (params.search !== undefined) {
2981+
loadSearch();
2982+
}
2983+
}
2984+
29722985
onHashChange(null);
29732986
window.onhashchange = onHashChange;
2987+
setupSearchLoader();
29742988
}());

0 commit comments

Comments
 (0)