diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs
index 99e96fdcf1eb4..ec04c94dc11f9 100644
--- a/src/librustdoc/html/layout.rs
+++ b/src/librustdoc/html/layout.rs
@@ -105,7 +105,7 @@ crate fn render<T: Print, S: Print>(
                            placeholder=\"Click or press ‘S’ to search, ‘?’ for more options…\" \
                            type=\"search\">\
                 </div>\
-                <button type=\"button\" class=\"help-button\">?</button>
+                <button type=\"button\" id=\"help-button\">?</button>
                 <a id=\"settings-menu\" href=\"{root_path}settings.html\">\
                     <img src=\"{static_root_path}wheel{suffix}.svg\" \
                          width=\"18\" height=\"18\" \
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index c2b40ab34e255..00a91e07d65e3 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -1347,6 +1347,7 @@ fn init_id_map() -> FxHashMap<String, usize> {
     map.insert("theme-picker".to_owned(), 1);
     map.insert("theme-choices".to_owned(), 1);
     map.insert("settings-menu".to_owned(), 1);
+    map.insert("help-button".to_owned(), 1);
     map.insert("main".to_owned(), 1);
     map.insert("search".to_owned(), 1);
     map.insert("crate-search".to_owned(), 1);
diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js
index dc65e14ab37b8..65f936819f589 100644
--- a/src/librustdoc/html/static/main.js
+++ b/src/librustdoc/html/static/main.js
@@ -381,56 +381,9 @@ function hideThemeButtonState() {
         }
     }
 
-    function highlightSourceLines(match, ev) {
-        if (typeof match === "undefined") {
-            // If we're in mobile mode, we should hide the sidebar in any case.
-            hideSidebar();
-            match = window.location.hash.match(/^#?(\d+)(?:-(\d+))?$/);
-        }
-        if (!match) {
-            return;
-        }
-        var from = parseInt(match[1], 10);
-        var to = from;
-        if (typeof match[2] !== "undefined") {
-            to = parseInt(match[2], 10);
-        }
-        if (to < from) {
-            var tmp = to;
-            to = from;
-            from = tmp;
-        }
-        var elem = document.getElementById(from);
-        if (!elem) {
-            return;
-        }
-        if (!ev) {
-            var x = document.getElementById(from);
-            if (x) {
-                x.scrollIntoView();
-            }
-        }
-        onEachLazy(document.getElementsByClassName("line-numbers"), function(e) {
-            onEachLazy(e.getElementsByTagName("span"), function(i_e) {
-                removeClass(i_e, "line-highlighted");
-            });
-        });
-        for (var i = from; i <= to; ++i) {
-            elem = document.getElementById(i);
-            if (!elem) {
-                break;
-            }
-            addClass(elem, "line-highlighted");
-        }
-    }
-
     function onHashChange(ev) {
         // If we're in mobile mode, we should hide the sidebar in any case.
         hideSidebar();
-        var match = window.location.hash.match(/^#?(\d+)(?:-(\d+))?$/);
-        if (match) {
-            return highlightSourceLines(match, ev);
-        }
         handleHashes(ev);
     }
 
@@ -585,78 +538,9 @@ function hideThemeButtonState() {
         }
     }
 
-    function findParentElement(elem, tagName) {
-        do {
-            if (elem && elem.tagName === tagName) {
-                return elem;
-            }
-            elem = elem.parentNode;
-        } while (elem);
-        return null;
-    }
-
     document.addEventListener("keypress", handleShortcut);
     document.addEventListener("keydown", handleShortcut);
 
-    var handleSourceHighlight = (function() {
-        var prev_line_id = 0;
-
-        var set_fragment = function(name) {
-            var x = window.scrollX,
-                y = window.scrollY;
-            if (searchState.browserSupportsHistoryApi()) {
-                history.replaceState(null, null, "#" + name);
-                highlightSourceLines();
-            } else {
-                location.replace("#" + name);
-            }
-            // Prevent jumps when selecting one or many lines
-            window.scrollTo(x, y);
-        };
-
-        return function(ev) {
-            var cur_line_id = parseInt(ev.target.id, 10);
-            ev.preventDefault();
-
-            if (ev.shiftKey && prev_line_id) {
-                // Swap selection if needed
-                if (prev_line_id > cur_line_id) {
-                    var tmp = prev_line_id;
-                    prev_line_id = cur_line_id;
-                    cur_line_id = tmp;
-                }
-
-                set_fragment(prev_line_id + "-" + cur_line_id);
-            } else {
-                prev_line_id = cur_line_id;
-
-                set_fragment(cur_line_id);
-            }
-        };
-    }());
-
-    document.addEventListener("click", function(ev) {
-        var helpElem = getHelpElement(false);
-        if (hasClass(ev.target, "help-button")) {
-            displayHelp(true, ev);
-        } else if (ev.target.tagName === "SPAN" && hasClass(ev.target.parentNode, "line-numbers")) {
-            handleSourceHighlight(ev);
-        } else if (helpElem && hasClass(helpElem, "hidden") === false) {
-            var is_inside_help_popup = ev.target !== helpElem && helpElem.contains(ev.target);
-            if (is_inside_help_popup === false) {
-                addClass(helpElem, "hidden");
-                removeClass(document.body, "blur");
-            }
-        } else {
-            // Making a collapsed element visible on onhashchange seems
-            // too late
-            var a = findParentElement(ev.target, "A");
-            if (a && a.hash) {
-                expandSection(a.hash.replace(/^#/, ""));
-            }
-        }
-    });
-
     (function() {
         var x = document.getElementsByClassName("version-selector");
         if (x.length > 0) {
@@ -1121,6 +1005,27 @@ function hideThemeButtonState() {
         });
     }());
 
+    function handleClick(id, f) {
+        var elem = document.getElementById(id);
+        if (elem) {
+            elem.addEventListener("click", f);
+        }
+    }
+    handleClick("help-button", function(ev) {
+        displayHelp(true, ev);
+    });
+
+    onEachLazy(document.getElementsByTagName("a"), function(el) {
+        // For clicks on internal links (<A> tags with a hash property), we expand the section we're
+        // jumping to *before* jumping there. We can't do this in onHashChange, because it changes
+        // the height of the document so we wind up scrolled to the wrong place.
+        if (el.hash) {
+            el.addEventListener("click", function() {
+                expandSection(el.hash.slice(1));
+            });
+        }
+    });
+
     onEachLazy(document.getElementsByClassName("notable-traits"), function(e) {
         e.onclick = function() {
             this.getElementsByClassName('notable-traits-tooltiptext')[0]
@@ -1165,6 +1070,13 @@ function hideThemeButtonState() {
         addClass(popup, "hidden");
         popup.id = "help";
 
+        popup.addEventListener("click", function(ev) {
+            if (ev.target === popup) {
+                // Clicked the blurred zone outside the help popup; dismiss help.
+                displayHelp(false, ev);
+            }
+        });
+
         var book_info = document.createElement("span");
         book_info.innerHTML = "You can find more information in \
             <a href=\"https://doc.rust-lang.org/rustdoc/\">the rustdoc book</a>.";
@@ -1223,7 +1135,7 @@ function hideThemeButtonState() {
     }
 
     onHashChange(null);
-    window.onhashchange = onHashChange;
+    window.addEventListener("hashchange", onHashChange);
     searchState.setup();
 }());
 
diff --git a/src/librustdoc/html/static/rustdoc.css b/src/librustdoc/html/static/rustdoc.css
index aaa2525644f11..6622756bcc36e 100644
--- a/src/librustdoc/html/static/rustdoc.css
+++ b/src/librustdoc/html/static/rustdoc.css
@@ -1289,7 +1289,7 @@ h4 > .notable-traits {
 	outline: none;
 }
 
-#settings-menu, .help-button {
+#settings-menu, #help-button {
 	position: absolute;
 	top: 10px;
 }
@@ -1299,7 +1299,7 @@ h4 > .notable-traits {
 	outline: none;
 }
 
-#theme-picker, #settings-menu, .help-button, #copy-path {
+#theme-picker, #settings-menu, #help-button, #copy-path {
 	padding: 4px;
 	width: 27px;
 	height: 29px;
@@ -1308,7 +1308,7 @@ h4 > .notable-traits {
 	cursor: pointer;
 }
 
-.help-button {
+#help-button {
 	right: 30px;
 	font-family: "Fira Sans", Arial, sans-serif;
 	text-align: center;
@@ -1593,7 +1593,7 @@ h4 > .notable-traits {
 	}
 
 	/* We don't display the help button on mobile devices. */
-	.help-button {
+	#help-button {
 		display: none;
 	}
 	.search-container > div {
diff --git a/src/librustdoc/html/static/source-script.js b/src/librustdoc/html/static/source-script.js
index 42b54e4cc1e46..a5aa2c5f67819 100644
--- a/src/librustdoc/html/static/source-script.js
+++ b/src/librustdoc/html/static/source-script.js
@@ -3,6 +3,7 @@
 
 // Local js definitions:
 /* global addClass, getCurrentValue, hasClass, removeClass, updateLocalStorage */
+(function() {
 
 function getCurrentFilePath() {
     var parts = window.location.pathname.split("/");
@@ -150,3 +151,99 @@ function createSourceSidebar() {
         selected_elem.focus();
     }
 }
+
+var lineNumbersRegex = /^#?(\d+)(?:-(\d+))?$/;
+
+function highlightSourceLines(match, ev) {
+    if (typeof match === "undefined") {
+        match = window.location.hash.match(lineNumbersRegex);
+    }
+    if (!match) {
+        return;
+    }
+    var from = parseInt(match[1], 10);
+    var to = from;
+    if (typeof match[2] !== "undefined") {
+        to = parseInt(match[2], 10);
+    }
+    if (to < from) {
+        var tmp = to;
+        to = from;
+        from = tmp;
+    }
+    var elem = document.getElementById(from);
+    if (!elem) {
+        return;
+    }
+    if (!ev) {
+        var x = document.getElementById(from);
+        if (x) {
+            x.scrollIntoView();
+        }
+    }
+    onEachLazy(document.getElementsByClassName("line-numbers"), function(e) {
+        onEachLazy(e.getElementsByTagName("span"), function(i_e) {
+            removeClass(i_e, "line-highlighted");
+        });
+    });
+    for (var i = from; i <= to; ++i) {
+        elem = document.getElementById(i);
+        if (!elem) {
+            break;
+        }
+        addClass(elem, "line-highlighted");
+    }
+}
+
+var handleSourceHighlight = (function() {
+    var prev_line_id = 0;
+
+    var set_fragment = function(name) {
+        var x = window.scrollX,
+            y = window.scrollY;
+        if (searchState.browserSupportsHistoryApi()) {
+            history.replaceState(null, null, "#" + name);
+            highlightSourceLines();
+        } else {
+            location.replace("#" + name);
+        }
+        // Prevent jumps when selecting one or many lines
+        window.scrollTo(x, y);
+    };
+
+    return function(ev) {
+        var cur_line_id = parseInt(ev.target.id, 10);
+        ev.preventDefault();
+
+        if (ev.shiftKey && prev_line_id) {
+            // Swap selection if needed
+            if (prev_line_id > cur_line_id) {
+                var tmp = prev_line_id;
+                prev_line_id = cur_line_id;
+                cur_line_id = tmp;
+            }
+
+            set_fragment(prev_line_id + "-" + cur_line_id);
+        } else {
+            prev_line_id = cur_line_id;
+
+            set_fragment(cur_line_id);
+        }
+    };
+}());
+
+window.addEventListener("hashchange", function() {
+    var match = window.location.hash.match(lineNumbersRegex);
+    if (match) {
+        return highlightSourceLines(match, ev);
+    }
+});
+
+onEachLazy(document.getElementsByClassName("line-numbers"), function(el) {
+    el.addEventListener("click", handleSourceHighlight);
+});
+
+highlightSourceLines();
+
+window.createSourceSidebar = createSourceSidebar;
+})();
diff --git a/src/librustdoc/html/static/themes/ayu.css b/src/librustdoc/html/static/themes/ayu.css
index aafb7f6300ea4..08148fdcf9597 100644
--- a/src/librustdoc/html/static/themes/ayu.css
+++ b/src/librustdoc/html/static/themes/ayu.css
@@ -503,7 +503,7 @@ kbd {
 	box-shadow-color: #c6cbd1;
 }
 
-#theme-picker, #settings-menu, .help-button, #copy-path {
+#theme-picker, #settings-menu, #help-button, #copy-path {
 	border-color: #5c6773;
 	background-color: #0f1419;
 	color: #fff;
@@ -515,7 +515,7 @@ kbd {
 
 #theme-picker:hover, #theme-picker:focus,
 #settings-menu:hover, #settings-menu:focus,
-.help-button:hover, .help-button:focus,
+#help-button:hover, #help-button:focus,
 #copy-path:hover, #copy-path:focus {
 	border-color: #e0e0e0;
 }
diff --git a/src/librustdoc/html/static/themes/dark.css b/src/librustdoc/html/static/themes/dark.css
index 715605d7b3785..8ee51d39c9214 100644
--- a/src/librustdoc/html/static/themes/dark.css
+++ b/src/librustdoc/html/static/themes/dark.css
@@ -393,7 +393,7 @@ kbd {
 	box-shadow-color: #c6cbd1;
 }
 
-#theme-picker, #settings-menu, .help-button, #copy-path {
+#theme-picker, #settings-menu, #help-button, #copy-path {
 	border-color: #e0e0e0;
 	background: #f0f0f0;
 	color: #000;
@@ -401,7 +401,7 @@ kbd {
 
 #theme-picker:hover, #theme-picker:focus,
 #settings-menu:hover, #settings-menu:focus,
-.help-button:hover, .help-button:focus,
+#help-button:hover, #help-button:focus,
 #copy-path:hover, #copy-path:focus {
 	border-color: #ffb900;
 }
diff --git a/src/librustdoc/html/static/themes/light.css b/src/librustdoc/html/static/themes/light.css
index 60ed889879387..e0efcfe2249af 100644
--- a/src/librustdoc/html/static/themes/light.css
+++ b/src/librustdoc/html/static/themes/light.css
@@ -385,14 +385,14 @@ kbd {
 	box-shadow-color: #c6cbd1;
 }
 
-#theme-picker, #settings-menu, .help-button, #copy-path {
+#theme-picker, #settings-menu, #help-button, #copy-path {
 	border-color: #e0e0e0;
 	background-color: #fff;
 }
 
 #theme-picker:hover, #theme-picker:focus,
 #settings-menu:hover, #settings-menu:focus,
-.help-button:hover, .help-button:focus,
+#help-button:hover, #help-button:focus,
 #copy-path:hover, #copy-path:focus {
 	border-color: #717171;
 }