Rollup merge of #82310 - jsha:rustdoc-search-onfocus, r=GuillaumeGomez

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).

Tested on Firefox and Chrome.

New:
https://jacob.hoffman-andrews.com/rust/search-on-demand/std/primitive.slice.html
https://jacob.hoffman-andrews.com/rust/search-on-demand/std/primitive.slice.html?search=fn

Old:
https://jacob.hoffman-andrews.com/rust/search-on-load/std/primitive.slice.html
https://jacob.hoffman-andrews.com/rust/search-on-load/std/primitive.slice.html?search=fn
This commit is contained in:
Yuki Okushi 2021-03-04 20:01:03 +09:00 committed by GitHub
commit 36b7bef1cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 51 deletions

View File

@ -58,6 +58,7 @@ crate fn render<T: Print, S: Print>(
{style_files}\
<script id=\"default-settings\"{default_settings}></script>\
<script src=\"{static_root_path}storage{suffix}.js\"></script>\
<script src=\"{static_root_path}crates{suffix}.js\"></script>\
<noscript><link rel=\"stylesheet\" href=\"{static_root_path}noscript{suffix}.css\"></noscript>\
{css_extension}\
{favicon}\
@ -112,10 +113,10 @@ crate fn render<T: Print, S: Print>(
<section id=\"search\" class=\"content hidden\"></section>\
<section class=\"footer\"></section>\
{after_content}\
<div id=\"rustdoc-vars\" data-root-path=\"{root_path}\" data-current-crate=\"{krate}\"></div>
<div id=\"rustdoc-vars\" data-root-path=\"{root_path}\" data-current-crate=\"{krate}\" \
data-search-js=\"{root_path}search-index{suffix}.js\"></div>
<script src=\"{static_root_path}main{suffix}.js\"></script>\
{extra_scripts}\
<script defer src=\"{root_path}search-index{suffix}.js\"></script>\
</body>\
</html>",
css_extension = if layout.css_file_extension.is_some() {

View File

@ -1061,10 +1061,12 @@ themePicker.onblur = handleThemeButtonsBlur;
cx.shared.fs.write(&dst, v.as_bytes())?;
}
// Update the search index
// Update the search index and crate list.
let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix));
let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name.as_str()), &dst);
all_indexes.push(search_index);
krates.push(krate.name.to_string());
krates.sort();
// Sort the indexes by crate so the file will be generated identically even
// with rustdoc running in parallel.
@ -1072,11 +1074,15 @@ themePicker.onblur = handleThemeButtonsBlur;
{
let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
v.push_str(&all_indexes.join(",\\\n"));
// "addSearchOptions" has to be called first so the crate filtering can be set before the
// search might start (if it's set into the URL for example).
v.push_str("\\\n}');\naddSearchOptions(searchIndex);initSearch(searchIndex);");
v.push_str("\\\n}');\ninitSearch(searchIndex);");
cx.shared.fs.write(&dst, &v)?;
}
let crate_list_dst = cx.dst.join(&format!("crates{}.js", cx.shared.resource_suffix));
let crate_list =
format!("window.ALL_CRATES = [{}];", krates.iter().map(|k| format!("\"{}\"", k)).join(","));
cx.shared.fs.write(&crate_list_dst, &crate_list)?;
if options.enable_index_page {
if let Some(index_page) = options.index_page.clone() {
let mut md_opts = options.clone();
@ -1098,9 +1104,6 @@ themePicker.onblur = handleThemeButtonsBlur;
extra_scripts: &[],
static_extra_scripts: &[],
};
krates.push(krate.name.to_string());
krates.sort();
krates.dedup();
let content = format!(
"<h1 class=\"fqn\">\

View File

@ -42,6 +42,7 @@ if (!DOMTokenList.prototype.remove) {
if (rustdocVars) {
window.rootPath = rustdocVars.attributes["data-root-path"].value;
window.currentCrate = rustdocVars.attributes["data-current-crate"].value;
window.searchJS = rustdocVars.attributes["data-search-js"].value;
}
var sidebarVars = document.getElementById("sidebar-vars");
if (sidebarVars) {
@ -1922,8 +1923,8 @@ function defocusSearchBar() {
return searchWords;
}
function startSearch() {
var callback = function() {
function registerSearchEvents() {
var searchAfter500ms = function() {
clearInputTimeout();
if (search_input.value.length === 0) {
if (browserSupportsHistoryApi()) {
@ -1935,8 +1936,8 @@ function defocusSearchBar() {
searchTimeout = setTimeout(search, 500);
}
};
search_input.onkeyup = callback;
search_input.oninput = callback;
search_input.onkeyup = searchAfter500ms;
search_input.oninput = searchAfter500ms;
document.getElementsByClassName("search-form")[0].onsubmit = function(e) {
e.preventDefault();
clearInputTimeout();
@ -1999,7 +2000,6 @@ function defocusSearchBar() {
}
});
}
search();
// This is required in firefox to avoid this problem: Navigating to a search result
// with the keyboard, hitting enter, and then hitting back would take you back to
@ -2017,8 +2017,14 @@ function defocusSearchBar() {
}
index = buildIndex(rawSearchIndex);
startSearch();
registerSearchEvents();
// If there's a search term in the URL, execute the search now.
if (getQueryStringParams().search) {
search();
}
};
function addSidebarCrates(crates) {
// Draw a convenient sidebar of known crates if we have a listing
if (window.rootPath === "../" || window.rootPath === "./") {
var sidebar = document.getElementsByClassName("sidebar-elems")[0];
@ -2029,14 +2035,6 @@ function defocusSearchBar() {
var ul = document.createElement("ul");
div.appendChild(ul);
var crates = [];
for (var crate in rawSearchIndex) {
if (!hasOwnProperty(rawSearchIndex, crate)) {
continue;
}
crates.push(crate);
}
crates.sort();
for (var i = 0; i < crates.length; ++i) {
var klass = "crate";
if (window.rootPath !== "./" && crates[i] === window.currentCrate) {
@ -2044,9 +2042,6 @@ function defocusSearchBar() {
}
var link = document.createElement("a");
link.href = window.rootPath + crates[i] + "/index.html";
// The summary in the search index has HTML, so we need to
// dynamically render it as plaintext.
link.title = convertHTMLToPlaintext(rawSearchIndex[crates[i]].doc);
link.className = klass;
link.textContent = crates[i];
@ -2057,7 +2052,7 @@ function defocusSearchBar() {
sidebar.appendChild(div);
}
}
};
}
/**
* Convert HTML to plaintext:
@ -2862,37 +2857,18 @@ function defocusSearchBar() {
}
}
window.addSearchOptions = function(crates) {
function addSearchOptions(crates) {
var elem = document.getElementById("crate-search");
if (!elem) {
enableSearchInput();
return;
}
var crates_text = [];
if (Object.keys(crates).length > 1) {
for (var crate in crates) {
if (hasOwnProperty(crates, crate)) {
crates_text.push(crate);
}
}
}
crates_text.sort(function(a, b) {
var lower_a = a.toLowerCase();
var lower_b = b.toLowerCase();
if (lower_a < lower_b) {
return -1;
} else if (lower_a > lower_b) {
return 1;
}
return 0;
});
var savedCrate = getSettingValue("saved-filter-crate");
for (var i = 0, len = crates_text.length; i < len; ++i) {
for (var i = 0, len = crates.length; i < len; ++i) {
var option = document.createElement("option");
option.value = crates_text[i];
option.innerText = crates_text[i];
option.value = crates[i];
option.innerText = crates[i];
elem.appendChild(option);
// Set the crate filter from saved storage, if the current page has the saved crate
// filter.
@ -2900,7 +2876,7 @@ function defocusSearchBar() {
// If not, ignore the crate filter -- we want to support filtering for crates on sites
// like doc.rust-lang.org where the crates may differ from page to page while on the
// same domain.
if (crates_text[i] === savedCrate) {
if (crates[i] === savedCrate) {
elem.value = savedCrate;
}
}
@ -2969,6 +2945,44 @@ function defocusSearchBar() {
buildHelperPopup = function() {};
}
function loadScript(url) {
var script = document.createElement('script');
script.src = url;
document.head.append(script);
}
function setupSearchLoader() {
var searchLoaded = false;
function loadSearch() {
if (!searchLoaded) {
searchLoaded = true;
loadScript(window.searchJS);
}
}
// `crates{version}.js` should always be loaded before this script, so we can use it safely.
addSearchOptions(window.ALL_CRATES);
addSidebarCrates(window.ALL_CRATES);
search_input.addEventListener("focus", function() {
search_input.origPlaceholder = search_input.placeholder;
search_input.placeholder = "Type your search here.";
loadSearch();
});
search_input.addEventListener("blur", function() {
search_input.placeholder = search_input.origPlaceholder;
});
enableSearchInput();
var crateSearchDropDown = document.getElementById("crate-search");
crateSearchDropDown.addEventListener("focus", loadSearch);
var params = getQueryStringParams();
if (params.search !== undefined) {
loadSearch();
}
}
onHashChange(null);
window.onhashchange = onHashChange;
setupSearchLoader();
}());