diff --git a/browser/components/extensions/parent/ext-tabs.js b/browser/components/extensions/parent/ext-tabs.js
index 0623d5683f72..48d69bc8131f 100644
--- a/browser/components/extensions/parent/ext-tabs.js
+++ b/browser/components/extensions/parent/ext-tabs.js
@@ -1638,12 +1638,12 @@ this.tabs = class extends ExtensionAPIPersistent {
goForward(tabId) {
let nativeTab = getTabOrActive(tabId);
- nativeTab.linkedBrowser.goForward();
+ nativeTab.linkedBrowser.goForward(false);
},
goBack(tabId) {
let nativeTab = getTabOrActive(tabId);
- nativeTab.linkedBrowser.goBack();
+ nativeTab.linkedBrowser.goBack(false);
},
},
};
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_goBack_goForward.js b/browser/components/extensions/test/browser/browser_ext_tabs_goBack_goForward.js
index 2ab960699d60..7c9f192341cc 100644
--- a/browser/components/extensions/test/browser/browser_ext_tabs_goBack_goForward.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_goBack_goForward.js
@@ -3,10 +3,6 @@
"use strict";
add_task(async function test_tabs_goBack_goForward() {
- await SpecialPowers.pushPrefEnv({
- set: [["browser.navigation.requireUserInteraction", false]],
- });
-
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
diff --git a/docshell/test/browser/browser.toml b/docshell/test/browser/browser.toml
index 3b2592a5a40e..ff8288575615 100644
--- a/docshell/test/browser/browser.toml
+++ b/docshell/test/browser/browser.toml
@@ -260,6 +260,8 @@ support-files = [
"file_csp_uir_dummy.html",
]
+["browser_current_entry_always_in_history_menu.js"]
+
["browser_dataURI_unique_opaque_origin.js"]
https_first_disabled = true
@@ -305,6 +307,9 @@ support-files = ["overlink_test.html"]
["browser_platform_emulation.js"]
+["browser_replace_state_during_navigation.js"]
+support-files = ["file_replace_state_during_navigation.html"]
+
["browser_search_notification.js"]
["browser_tab_replace_while_loading.js"]
diff --git a/docshell/test/browser/browser_current_entry_always_in_history_menu.js b/docshell/test/browser/browser_current_entry_always_in_history_menu.js
new file mode 100644
index 000000000000..1ffb891a3463
--- /dev/null
+++ b/docshell/test/browser/browser_current_entry_always_in_history_menu.js
@@ -0,0 +1,37 @@
+"use strict";
+
+const TEST_URI = "https://example.com/";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.navigation.requireUserInteraction", true]],
+ });
+});
+
+add_task(async () => {
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Navigate away, after causing a user interaction.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ await followLink(TEST_URI + "2.html");
+
+ // Navigate again, without causing a user interaction.
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.history.pushState({}, "", "https://example.com/3.html");
+ });
+
+ // Wait for the session data to be flushed before continuing the test
+ await new Promise(resolve =>
+ SessionStore.getSessionHistory(gBrowser.selectedTab, resolve)
+ );
+ // The entry with no interaction shouldn't appear.
+ await assertMenulist([TEST_URI + "3.html", TEST_URI]);
+
+ // Go back using history.back, which does not check for user interaction.
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.history.back();
+ });
+
+ // We are back on entry 2, so it should appear in the list.
+ await assertMenulist([TEST_URI + "3.html", TEST_URI + "2.html", TEST_URI]);
+ });
+});
diff --git a/docshell/test/browser/browser_replace_state_during_navigation.js b/docshell/test/browser/browser_replace_state_during_navigation.js
new file mode 100644
index 000000000000..0554e53a7e55
--- /dev/null
+++ b/docshell/test/browser/browser_replace_state_during_navigation.js
@@ -0,0 +1,38 @@
+"use strict";
+
+const TEST_URI =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "dummy_page.html";
+const TEST_URI_2 =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "file_replace_state_during_navigation.html";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.navigation.requireUserInteraction", true]],
+ });
+});
+
+add_task(async () => {
+ await BrowserTestUtils.withNewTab(TEST_URI, async browser => {
+ // Add user interaction to the first page.
+ await BrowserTestUtils.synthesizeMouseAtCenter("body", {}, browser);
+
+ // Follow link to the next page.
+ await followLink(TEST_URI_2);
+
+ // Navigate, causing a hashchange event to fire and call history.replaceState
+ await BrowserTestUtils.synthesizeMouseAtCenter("#link", {}, browser);
+
+ await assertMenulist([
+ TEST_URI_2 + "#1",
+ TEST_URI_2 + "#inject",
+ TEST_URI_2,
+ TEST_URI,
+ ]);
+ });
+});
diff --git a/docshell/test/browser/file_replace_state_during_navigation.html b/docshell/test/browser/file_replace_state_during_navigation.html
new file mode 100644
index 000000000000..09b0de9e05ae
--- /dev/null
+++ b/docshell/test/browser/file_replace_state_during_navigation.html
@@ -0,0 +1,8 @@
+
+
+link
diff --git a/docshell/test/browser/head.js b/docshell/test/browser/head.js
index 00b17d89a9b0..a2158586789a 100644
--- a/docshell/test/browser/head.js
+++ b/docshell/test/browser/head.js
@@ -195,3 +195,51 @@ class SHListener {
});
}
}
+
+async function assertMenulist(entries) {
+ // Wait for the session data to be flushed before continuing the test
+ await new Promise(resolve =>
+ SessionStore.getSessionHistory(gBrowser.selectedTab, resolve)
+ );
+
+ let backButton = document.getElementById("back-button");
+ let contextMenu = document.getElementById("backForwardMenu");
+
+ info("waiting for the history menu to open");
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ EventUtils.synthesizeMouseAtCenter(backButton, {
+ type: "contextmenu",
+ button: 2,
+ });
+ await popupShownPromise;
+
+ info("history menu opened");
+
+ let nodes = contextMenu.childNodes;
+
+ is(
+ nodes.length,
+ entries.length,
+ "Has the expected number of contextMenu entries"
+ );
+
+ for (let i = 0; i < entries.length; i++) {
+ let node = nodes[i];
+ is(
+ node.getAttribute("uri"),
+ entries[i],
+ "contextMenu node has the correct uri"
+ );
+ }
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ contextMenu.hidePopup();
+ await popupHiddenPromise;
+}
diff --git a/mobile/shared/components/extensions/ext-tabs.js b/mobile/shared/components/extensions/ext-tabs.js
index c4d8010e7937..70c1cff8a04d 100644
--- a/mobile/shared/components/extensions/ext-tabs.js
+++ b/mobile/shared/components/extensions/ext-tabs.js
@@ -580,12 +580,12 @@ this.tabs = class extends ExtensionAPIPersistent {
goForward(tabId) {
const { browser } = getTabOrActive(tabId);
- browser.goForward();
+ browser.goForward(false);
},
goBack(tabId) {
const { browser } = getTabOrActive(tabId);
- browser.goBack();
+ browser.goBack(false);
},
},
};