summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/contextMenu
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/base/content/test/contextMenu/browser.ini91
-rw-r--r--browser/base/content/test/contextMenu/browser_bug1798178.js89
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu.js1943
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_badiframe.js182
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_contenteditable.js118
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_iframe.js73
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_input.js387
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_inspect.js61
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_keyword.js198
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_linkopen.js109
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_loadblobinnewtab.html56
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_loadblobinnewtab.js186
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_save_blocked.js78
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_share_macosx.js144
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_share_win.js77
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_shareurl.html2
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_spellcheck.js334
-rw-r--r--browser/base/content/test/contextMenu/browser_contextmenu_touch.js94
-rw-r--r--browser/base/content/test/contextMenu/browser_copy_image_link.js40
-rw-r--r--browser/base/content/test/contextMenu/browser_strip_on_share_link.js151
-rw-r--r--browser/base/content/test/contextMenu/browser_utilityOverlay.js78
-rw-r--r--browser/base/content/test/contextMenu/browser_utilityOverlayPrincipal.js72
-rw-r--r--browser/base/content/test/contextMenu/browser_view_image.js197
-rw-r--r--browser/base/content/test/contextMenu/bug1798178.sjs9
-rw-r--r--browser/base/content/test/contextMenu/contextmenu_common.js437
-rw-r--r--browser/base/content/test/contextMenu/ctxmenu-image.pngbin0 -> 5401 bytes
-rw-r--r--browser/base/content/test/contextMenu/doggy.pngbin0 -> 46876 bytes
-rw-r--r--browser/base/content/test/contextMenu/file_bug1798178.html5
-rw-r--r--browser/base/content/test/contextMenu/firebird.pngbin0 -> 16179 bytes
-rw-r--r--browser/base/content/test/contextMenu/firebird.png^headers^2
-rw-r--r--browser/base/content/test/contextMenu/subtst_contextmenu.html61
-rw-r--r--browser/base/content/test/contextMenu/subtst_contextmenu_input.html30
-rw-r--r--browser/base/content/test/contextMenu/subtst_contextmenu_keyword.html17
-rw-r--r--browser/base/content/test/contextMenu/subtst_contextmenu_webext.html12
-rw-r--r--browser/base/content/test/contextMenu/subtst_contextmenu_xul.xhtml9
-rw-r--r--browser/base/content/test/contextMenu/test_contextmenu_iframe.html11
-rw-r--r--browser/base/content/test/contextMenu/test_contextmenu_links.html14
-rw-r--r--browser/base/content/test/contextMenu/test_view_image_inline_svg.html15
-rw-r--r--browser/base/content/test/contextMenu/test_view_image_revoked_cached_blob.html40
39 files changed, 5422 insertions, 0 deletions
diff --git a/browser/base/content/test/contextMenu/browser.ini b/browser/base/content/test/contextMenu/browser.ini
new file mode 100644
index 0000000000..df0a013059
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser.ini
@@ -0,0 +1,91 @@
+[DEFAULT]
+support-files =
+ subtst_contextmenu_webext.html
+ test_contextmenu_links.html
+ subtst_contextmenu.html
+ subtst_contextmenu_input.html
+ subtst_contextmenu_keyword.html
+ subtst_contextmenu_xul.xhtml
+ ctxmenu-image.png
+ ../general/head.js
+ ../general/video.ogg
+ ../general/audio.ogg
+ ../../../../../toolkit/components/pdfjs/test/file_pdfjs_test.pdf
+ contextmenu_common.js
+ file_bug1798178.html
+ bug1798178.sjs
+
+[browser_bug1798178.js]
+[browser_contextmenu.js]
+tags = fullscreen
+skip-if =
+ os == "linux"
+ verify
+[browser_contextmenu_badiframe.js]
+https_first_disabled = true
+skip-if =
+ os == "win" # Bug 1719856
+ os == "linux" && socketprocess_networking
+[browser_contextmenu_contenteditable.js]
+[browser_contextmenu_iframe.js]
+support-files =
+ test_contextmenu_iframe.html
+skip-if =
+ os == "linux" && socketprocess_networking
+[browser_contextmenu_input.js]
+skip-if =
+ os == "linux"
+[browser_contextmenu_inspect.js]
+skip-if =
+ os == "linux" && socketprocess_networking
+[browser_contextmenu_keyword.js]
+skip-if =
+ os == "linux" # disabled on Linux due to bug 513558
+[browser_contextmenu_linkopen.js]
+skip-if =
+ os == "linux" && socketprocess_networking
+[browser_contextmenu_loadblobinnewtab.js]
+support-files = browser_contextmenu_loadblobinnewtab.html
+skip-if =
+ os == "linux" && socketprocess_networking
+[browser_contextmenu_save_blocked.js]
+skip-if =
+ os == "linux" && socketprocess_networking
+[browser_contextmenu_share_macosx.js]
+support-files =
+ browser_contextmenu_shareurl.html
+run-if =
+ os == "mac"
+[browser_contextmenu_share_win.js]
+https_first_disabled = true
+support-files =
+ browser_contextmenu_shareurl.html
+run-if =
+ os == "win"
+[browser_contextmenu_spellcheck.js]
+https_first_disabled = true
+skip-if =
+ os == "linux"
+ debug # bug 1798233 - this trips assertions that seem harmless in opt and unlikely to occur in practical use.
+[browser_contextmenu_touch.js]
+skip-if = true # Bug 1424433, disable due to very high frequency failure rate also on Windows 10
+[browser_copy_image_link.js]
+support-files =
+ doggy.png
+ firebird.png
+ firebird.png^headers^
+skip-if =
+ os == "linux" && socketprocess_networking
+[browser_strip_on_share_link.js]
+[browser_utilityOverlay.js]
+https_first_disabled = true
+skip-if =
+ os == "linux" && socketprocess_networking
+[browser_utilityOverlayPrincipal.js]
+https_first_disabled = true
+[browser_view_image.js]
+support-files =
+ test_view_image_revoked_cached_blob.html
+ test_view_image_inline_svg.html
+skip-if =
+ os == "linux" && socketprocess_networking
diff --git a/browser/base/content/test/contextMenu/browser_bug1798178.js b/browser/base/content/test/contextMenu/browser_bug1798178.js
new file mode 100644
index 0000000000..529665a6f9
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_bug1798178.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this
+);
+
+const TEST_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "file_bug1798178.html";
+
+let MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+function createTemporarySaveDirectory() {
+ let saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists()) {
+ info("create testsavedir!");
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+ info("return from createTempSaveDir: " + saveDir.path);
+ return saveDir;
+}
+
+add_task(async function test_save_link_cross_origin() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.opaqueResponseBlocking", true]],
+ });
+ await BrowserTestUtils.withNewTab(TEST_URL, async browser => {
+ let menu = document.getElementById("contentAreaContextMenu");
+ let popupShown = BrowserTestUtils.waitForEvent(menu, "popupshown");
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "a[href]",
+ { type: "contextmenu", button: 2 },
+ browser
+ );
+ await popupShown;
+
+ let filePickerShow = new Promise(r => {
+ MockFilePicker.showCallback = function (fp) {
+ ok(true, "filepicker should be shown");
+ info("MockFilePicker showCallback");
+
+ let fileName = fp.defaultString;
+ destFile = tempDir.clone();
+ destFile.append(fileName);
+
+ MockFilePicker.setFiles([destFile]);
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+
+ info("MockFilePicker showCallback done");
+ r();
+ };
+ });
+
+ info("Let's create a temporary dir");
+ let tempDir = createTemporarySaveDirectory();
+ let destFile;
+
+ MockFilePicker.displayDirectory = tempDir;
+
+ let transferCompletePromise = new Promise(resolve => {
+ function onTransferComplete(downloadSuccess) {
+ ok(downloadSuccess, "File should have been downloaded successfully");
+ resolve();
+ }
+ mockTransferCallback = onTransferComplete;
+ mockTransferRegisterer.register();
+ });
+
+ let saveLinkCommand = document.getElementById("context-savelink");
+ info("saveLinkCommand: " + saveLinkCommand);
+ saveLinkCommand.doCommand();
+
+ await filePickerShow;
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden");
+ menu.hidePopup();
+ await popupHiddenPromise;
+
+ await transferCompletePromise;
+ });
+});
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu.js b/browser/base/content/test/contextMenu/browser_contextmenu.js
new file mode 100644
index 0000000000..03a848f26d
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu.js
@@ -0,0 +1,1943 @@
+"use strict";
+
+let contextMenu;
+let LOGIN_FILL_ITEMS = ["---", null, "manage-saved-logins", true];
+let NAVIGATION_ITEMS =
+ AppConstants.platform == "macosx"
+ ? [
+ "context-back",
+ false,
+ "context-forward",
+ false,
+ "context-reload",
+ true,
+ "---",
+ null,
+ "context-bookmarkpage",
+ true,
+ ]
+ : [
+ "context-navigation",
+ null,
+ [
+ "context-back",
+ false,
+ "context-forward",
+ false,
+ "context-reload",
+ true,
+ "context-bookmarkpage",
+ true,
+ ],
+ null,
+ "---",
+ null,
+ ];
+let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
+let hasContainers =
+ Services.prefs.getBoolPref("privacy.userContext.enabled") &&
+ ContextualIdentityService.getPublicIdentities().length;
+
+const example_base =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/browser/browser/base/content/test/contextMenu/";
+const chrome_base =
+ "chrome://mochitests/content/browser/browser/base/content/test/contextMenu/";
+const head_base =
+ "chrome://mochitests/content/browser/browser/base/content/test/contextMenu/";
+
+/* import-globals-from contextmenu_common.js */
+Services.scriptloader.loadSubScript(
+ chrome_base + "contextmenu_common.js",
+ this
+);
+
+function getThisFrameSubMenu(base_menu) {
+ if (AppConstants.NIGHTLY_BUILD) {
+ let osPidItem = ["context-frameOsPid", false];
+ base_menu = base_menu.concat(osPidItem);
+ }
+ return base_menu;
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", true],
+ ["extensions.screenshots.disabled", false],
+ ["layout.forms.reveal-password-context-menu.enabled", true],
+ ],
+ });
+});
+
+// Below are test cases for XUL element
+add_task(async function test_xul_text_link_label() {
+ let url = chrome_base + "subtst_contextmenu_xul.xhtml";
+
+ await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url,
+ waitForLoad: true,
+ waitForStateStop: true,
+ });
+
+ await test_contextmenu("#test-xul-text-link-label", [
+ "context-openlinkintab",
+ true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink",
+ true,
+ "context-openlinkprivate",
+ true,
+ "---",
+ null,
+ "context-bookmarklink",
+ true,
+ "context-savelink",
+ true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+ ]);
+
+ // Clean up so won't affect HTML element test cases.
+ lastElementSelector = null;
+ gBrowser.removeCurrentTab();
+});
+
+// Below are test cases for HTML element.
+
+add_task(async function test_setup_html() {
+ let url = example_base + "subtst_contextmenu.html";
+
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ let doc = content.document;
+ let audioIframe = doc.querySelector("#test-audio-in-iframe");
+ // media documents always use a <video> tag.
+ let audio = audioIframe.contentDocument.querySelector("video");
+ let videoIframe = doc.querySelector("#test-video-in-iframe");
+ let video = videoIframe.contentDocument.querySelector("video");
+
+ audio.loop = true;
+ audio.src = "audio.ogg";
+ video.loop = true;
+ video.src = "video.ogg";
+
+ let awaitPause = ContentTaskUtils.waitForEvent(audio, "pause");
+ await ContentTaskUtils.waitForCondition(
+ () => !audio.paused,
+ "Making sure audio is playing before calling pause"
+ );
+ audio.pause();
+ await awaitPause;
+
+ awaitPause = ContentTaskUtils.waitForEvent(video, "pause");
+ await ContentTaskUtils.waitForCondition(
+ () => !video.paused,
+ "Making sure video is playing before calling pause"
+ );
+ video.pause();
+ await awaitPause;
+ });
+});
+
+let plainTextItems;
+add_task(async function test_plaintext() {
+ await test_contextmenu("#test-text", [
+ ...NAVIGATION_ITEMS,
+ "context-savepage",
+ true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "context-selectall",
+ true,
+ "---",
+ null,
+ "context-take-screenshot",
+ true,
+ "---",
+ null,
+ "context-viewsource",
+ true,
+ ]);
+});
+
+const kLinkItems = [
+ "context-openlinkintab",
+ true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink",
+ true,
+ "context-openlinkprivate",
+ true,
+ "---",
+ null,
+ "context-bookmarklink",
+ true,
+ "context-savelink",
+ true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+];
+
+add_task(async function test_link() {
+ await test_contextmenu("#test-link", kLinkItems);
+});
+
+add_task(async function test_link_in_shadow_dom() {
+ await test_contextmenu("#shadow-host", kLinkItems, {
+ offsetX: 6,
+ offsetY: 6,
+ });
+});
+
+add_task(async function test_link_over_shadow_dom() {
+ await test_contextmenu("#shadow-host-in-link", kLinkItems, {
+ offsetX: 6,
+ offsetY: 6,
+ });
+});
+
+add_task(async function test_mailto() {
+ await test_contextmenu("#test-mailto", [
+ "context-copyemail",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+ ]);
+});
+
+add_task(async function test_tel() {
+ await test_contextmenu("#test-tel", [
+ "context-copyphone",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+ ]);
+});
+
+add_task(async function test_image() {
+ for (let selector of ["#test-image", "#test-svg-image"]) {
+ await test_contextmenu(
+ selector,
+ [
+ "context-viewimage",
+ true,
+ "context-saveimage",
+ true,
+ "context-copyimage-contents",
+ true,
+ "context-copyimage",
+ true,
+ "context-sendimage",
+ true,
+ ...getTextRecognitionItems(),
+ ...(Services.prefs.getBoolPref("browser.menu.showViewImageInfo", false)
+ ? ["context-viewimageinfo", true]
+ : []),
+ "---",
+ null,
+ "context-setDesktopBackground",
+ true,
+ ],
+ {
+ onContextMenuShown() {
+ is(
+ typeof gContextMenu.imageInfo.height,
+ "number",
+ "Should have height"
+ );
+ is(
+ typeof gContextMenu.imageInfo.width,
+ "number",
+ "Should have width"
+ );
+ },
+ }
+ );
+ }
+});
+
+add_task(async function test_canvas() {
+ await test_contextmenu("#test-canvas", [
+ "context-viewimage",
+ true,
+ "context-saveimage",
+ true,
+ "---",
+ null,
+ "context-selectall",
+ true,
+ "---",
+ null,
+ "context-take-screenshot",
+ true,
+ ]);
+});
+
+add_task(async function test_video_ok() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.videocontrols.picture-in-picture.enabled", true]],
+ });
+
+ await test_contextmenu("#test-video-ok", [
+ "context-media-play",
+ true,
+ "context-media-mute",
+ true,
+ "context-media-playbackrate",
+ null,
+ [
+ "context-media-playbackrate-050x",
+ true,
+ "context-media-playbackrate-100x",
+ true,
+ "context-media-playbackrate-125x",
+ true,
+ "context-media-playbackrate-150x",
+ true,
+ "context-media-playbackrate-200x",
+ true,
+ ],
+ null,
+ "context-media-loop",
+ true,
+ "context-video-fullscreen",
+ true,
+ "context-media-hidecontrols",
+ true,
+ "---",
+ null,
+ "context-viewvideo",
+ true,
+ "context-video-pictureinpicture",
+ true,
+ "---",
+ null,
+ "context-video-saveimage",
+ true,
+ "context-savevideo",
+ true,
+ "context-copyvideourl",
+ true,
+ "context-sendvideo",
+ true,
+ ]);
+
+ await SpecialPowers.popPrefEnv();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.videocontrols.picture-in-picture.enabled", false]],
+ });
+
+ await test_contextmenu("#test-video-ok", [
+ "context-media-play",
+ true,
+ "context-media-mute",
+ true,
+ "context-media-playbackrate",
+ null,
+ [
+ "context-media-playbackrate-050x",
+ true,
+ "context-media-playbackrate-100x",
+ true,
+ "context-media-playbackrate-125x",
+ true,
+ "context-media-playbackrate-150x",
+ true,
+ "context-media-playbackrate-200x",
+ true,
+ ],
+ null,
+ "context-media-loop",
+ true,
+ "context-video-fullscreen",
+ true,
+ "context-media-hidecontrols",
+ true,
+ "---",
+ null,
+ "context-viewvideo",
+ true,
+ "---",
+ null,
+ "context-video-saveimage",
+ true,
+ "context-savevideo",
+ true,
+ "context-copyvideourl",
+ true,
+ "context-sendvideo",
+ true,
+ ]);
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_audio_in_video() {
+ await test_contextmenu("#test-audio-in-video", [
+ "context-media-play",
+ true,
+ "context-media-mute",
+ true,
+ "context-media-playbackrate",
+ null,
+ [
+ "context-media-playbackrate-050x",
+ true,
+ "context-media-playbackrate-100x",
+ true,
+ "context-media-playbackrate-125x",
+ true,
+ "context-media-playbackrate-150x",
+ true,
+ "context-media-playbackrate-200x",
+ true,
+ ],
+ null,
+ "context-media-loop",
+ true,
+ "context-media-showcontrols",
+ true,
+ "---",
+ null,
+ "context-saveaudio",
+ true,
+ "context-copyaudiourl",
+ true,
+ "context-sendaudio",
+ true,
+ ]);
+});
+
+add_task(async function test_video_bad() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.videocontrols.picture-in-picture.enabled", true]],
+ });
+
+ await test_contextmenu("#test-video-bad", [
+ "context-media-play",
+ false,
+ "context-media-mute",
+ false,
+ "context-media-playbackrate",
+ null,
+ [
+ "context-media-playbackrate-050x",
+ false,
+ "context-media-playbackrate-100x",
+ false,
+ "context-media-playbackrate-125x",
+ false,
+ "context-media-playbackrate-150x",
+ false,
+ "context-media-playbackrate-200x",
+ false,
+ ],
+ null,
+ "context-media-loop",
+ true,
+ "context-video-fullscreen",
+ false,
+ "context-media-hidecontrols",
+ false,
+ "---",
+ null,
+ "context-viewvideo",
+ true,
+ "---",
+ null,
+ "context-video-saveimage",
+ false,
+ "context-savevideo",
+ true,
+ "context-copyvideourl",
+ true,
+ "context-sendvideo",
+ true,
+ ]);
+
+ await SpecialPowers.popPrefEnv();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.videocontrols.picture-in-picture.enabled", false]],
+ });
+
+ await test_contextmenu("#test-video-bad", [
+ "context-media-play",
+ false,
+ "context-media-mute",
+ false,
+ "context-media-playbackrate",
+ null,
+ [
+ "context-media-playbackrate-050x",
+ false,
+ "context-media-playbackrate-100x",
+ false,
+ "context-media-playbackrate-125x",
+ false,
+ "context-media-playbackrate-150x",
+ false,
+ "context-media-playbackrate-200x",
+ false,
+ ],
+ null,
+ "context-media-loop",
+ true,
+ "context-video-fullscreen",
+ false,
+ "context-media-hidecontrols",
+ false,
+ "---",
+ null,
+ "context-viewvideo",
+ true,
+ "---",
+ null,
+ "context-video-saveimage",
+ false,
+ "context-savevideo",
+ true,
+ "context-copyvideourl",
+ true,
+ "context-sendvideo",
+ true,
+ ]);
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_video_bad2() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.videocontrols.picture-in-picture.enabled", true]],
+ });
+
+ await test_contextmenu("#test-video-bad2", [
+ "context-media-play",
+ false,
+ "context-media-mute",
+ false,
+ "context-media-playbackrate",
+ null,
+ [
+ "context-media-playbackrate-050x",
+ false,
+ "context-media-playbackrate-100x",
+ false,
+ "context-media-playbackrate-125x",
+ false,
+ "context-media-playbackrate-150x",
+ false,
+ "context-media-playbackrate-200x",
+ false,
+ ],
+ null,
+ "context-media-loop",
+ true,
+ "context-video-fullscreen",
+ false,
+ "context-media-hidecontrols",
+ false,
+ "---",
+ null,
+ "context-viewvideo",
+ false,
+ "---",
+ null,
+ "context-video-saveimage",
+ false,
+ "context-savevideo",
+ false,
+ "context-copyvideourl",
+ false,
+ "context-sendvideo",
+ false,
+ ]);
+
+ await SpecialPowers.popPrefEnv();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.videocontrols.picture-in-picture.enabled", false]],
+ });
+
+ await test_contextmenu("#test-video-bad2", [
+ "context-media-play",
+ false,
+ "context-media-mute",
+ false,
+ "context-media-playbackrate",
+ null,
+ [
+ "context-media-playbackrate-050x",
+ false,
+ "context-media-playbackrate-100x",
+ false,
+ "context-media-playbackrate-125x",
+ false,
+ "context-media-playbackrate-150x",
+ false,
+ "context-media-playbackrate-200x",
+ false,
+ ],
+ null,
+ "context-media-loop",
+ true,
+ "context-video-fullscreen",
+ false,
+ "context-media-hidecontrols",
+ false,
+ "---",
+ null,
+ "context-viewvideo",
+ false,
+ "---",
+ null,
+ "context-video-saveimage",
+ false,
+ "context-savevideo",
+ false,
+ "context-copyvideourl",
+ false,
+ "context-sendvideo",
+ false,
+ ]);
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_iframe() {
+ await test_contextmenu("#test-iframe", [
+ ...NAVIGATION_ITEMS,
+ "context-savepage",
+ true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "context-selectall",
+ true,
+ "---",
+ null,
+ "frame",
+ null,
+ getThisFrameSubMenu([
+ "context-showonlythisframe",
+ true,
+ "context-openframeintab",
+ true,
+ "context-openframe",
+ true,
+ "---",
+ null,
+ "context-reloadframe",
+ true,
+ "---",
+ null,
+ "context-bookmarkframe",
+ true,
+ "context-saveframe",
+ true,
+ "---",
+ null,
+ "context-printframe",
+ true,
+ "---",
+ null,
+ "context-take-frame-screenshot",
+ true,
+ "---",
+ null,
+ "context-viewframesource",
+ true,
+ "context-viewframeinfo",
+ true,
+ ]),
+ null,
+ "---",
+ null,
+ "context-viewsource",
+ true,
+ ]);
+});
+
+add_task(async function test_video_in_iframe() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.videocontrols.picture-in-picture.enabled", true]],
+ });
+
+ await test_contextmenu("#test-video-in-iframe", [
+ "context-media-play",
+ true,
+ "context-media-mute",
+ true,
+ "context-media-playbackrate",
+ null,
+ [
+ "context-media-playbackrate-050x",
+ true,
+ "context-media-playbackrate-100x",
+ true,
+ "context-media-playbackrate-125x",
+ true,
+ "context-media-playbackrate-150x",
+ true,
+ "context-media-playbackrate-200x",
+ true,
+ ],
+ null,
+ "context-media-loop",
+ true,
+ "context-video-fullscreen",
+ true,
+ "context-media-hidecontrols",
+ true,
+ "---",
+ null,
+ "context-viewvideo",
+ true,
+ "context-video-pictureinpicture",
+ true,
+ "---",
+ null,
+ "context-video-saveimage",
+ true,
+ "context-savevideo",
+ true,
+ "context-copyvideourl",
+ true,
+ "context-sendvideo",
+ true,
+ "---",
+ null,
+ "frame",
+ null,
+ getThisFrameSubMenu([
+ "context-showonlythisframe",
+ true,
+ "context-openframeintab",
+ true,
+ "context-openframe",
+ true,
+ "---",
+ null,
+ "context-reloadframe",
+ true,
+ "---",
+ null,
+ "context-bookmarkframe",
+ true,
+ "context-saveframe",
+ true,
+ "---",
+ null,
+ "context-printframe",
+ true,
+ "---",
+ null,
+ "context-viewframeinfo",
+ true,
+ ]),
+ null,
+ ]);
+
+ await SpecialPowers.popPrefEnv();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.videocontrols.picture-in-picture.enabled", false]],
+ });
+
+ await test_contextmenu("#test-video-in-iframe", [
+ "context-media-play",
+ true,
+ "context-media-mute",
+ true,
+ "context-media-playbackrate",
+ null,
+ [
+ "context-media-playbackrate-050x",
+ true,
+ "context-media-playbackrate-100x",
+ true,
+ "context-media-playbackrate-125x",
+ true,
+ "context-media-playbackrate-150x",
+ true,
+ "context-media-playbackrate-200x",
+ true,
+ ],
+ null,
+ "context-media-loop",
+ true,
+ "context-video-fullscreen",
+ true,
+ "context-media-hidecontrols",
+ true,
+ "---",
+ null,
+ "context-viewvideo",
+ true,
+ "---",
+ null,
+ "context-video-saveimage",
+ true,
+ "context-savevideo",
+ true,
+ "context-copyvideourl",
+ true,
+ "context-sendvideo",
+ true,
+ "---",
+ null,
+ "frame",
+ null,
+ getThisFrameSubMenu([
+ "context-showonlythisframe",
+ true,
+ "context-openframeintab",
+ true,
+ "context-openframe",
+ true,
+ "---",
+ null,
+ "context-reloadframe",
+ true,
+ "---",
+ null,
+ "context-bookmarkframe",
+ true,
+ "context-saveframe",
+ true,
+ "---",
+ null,
+ "context-printframe",
+ true,
+ "---",
+ null,
+ "context-viewframeinfo",
+ true,
+ ]),
+ null,
+ ]);
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_audio_in_iframe() {
+ await test_contextmenu("#test-audio-in-iframe", [
+ "context-media-play",
+ true,
+ "context-media-mute",
+ true,
+ "context-media-playbackrate",
+ null,
+ [
+ "context-media-playbackrate-050x",
+ true,
+ "context-media-playbackrate-100x",
+ true,
+ "context-media-playbackrate-125x",
+ true,
+ "context-media-playbackrate-150x",
+ true,
+ "context-media-playbackrate-200x",
+ true,
+ ],
+ null,
+ "context-media-loop",
+ true,
+ "---",
+ null,
+ "context-saveaudio",
+ true,
+ "context-copyaudiourl",
+ true,
+ "context-sendaudio",
+ true,
+ "---",
+ null,
+ "frame",
+ null,
+ getThisFrameSubMenu([
+ "context-showonlythisframe",
+ true,
+ "context-openframeintab",
+ true,
+ "context-openframe",
+ true,
+ "---",
+ null,
+ "context-reloadframe",
+ true,
+ "---",
+ null,
+ "context-bookmarkframe",
+ true,
+ "context-saveframe",
+ true,
+ "---",
+ null,
+ "context-printframe",
+ true,
+ "---",
+ null,
+ "context-viewframeinfo",
+ true,
+ ]),
+ null,
+ ]);
+});
+
+add_task(async function test_image_in_iframe() {
+ await test_contextmenu("#test-image-in-iframe", [
+ "context-viewimage",
+ true,
+ "context-saveimage",
+ true,
+ "context-copyimage-contents",
+ true,
+ "context-copyimage",
+ true,
+ "context-sendimage",
+ true,
+ ...getTextRecognitionItems(),
+ ...(Services.prefs.getBoolPref("browser.menu.showViewImageInfo", false)
+ ? ["context-viewimageinfo", true]
+ : []),
+ "---",
+ null,
+ "context-setDesktopBackground",
+ true,
+ "---",
+ null,
+ "frame",
+ null,
+ getThisFrameSubMenu([
+ "context-showonlythisframe",
+ true,
+ "context-openframeintab",
+ true,
+ "context-openframe",
+ true,
+ "---",
+ null,
+ "context-reloadframe",
+ true,
+ "---",
+ null,
+ "context-bookmarkframe",
+ true,
+ "context-saveframe",
+ true,
+ "---",
+ null,
+ "context-printframe",
+ true,
+ "---",
+ null,
+ "context-viewframeinfo",
+ true,
+ ]),
+ null,
+ ]);
+});
+
+add_task(async function test_pdf_viewer_in_iframe() {
+ await test_contextmenu(
+ "#test-pdf-viewer-in-frame",
+ [
+ ...NAVIGATION_ITEMS,
+ "context-savepage",
+ true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "context-selectall",
+ true,
+ "---",
+ null,
+ "frame",
+ null,
+ getThisFrameSubMenu([
+ "context-showonlythisframe",
+ true,
+ "context-openframeintab",
+ true,
+ "context-openframe",
+ true,
+ "---",
+ null,
+ "context-reloadframe",
+ true,
+ "---",
+ null,
+ "context-bookmarkframe",
+ true,
+ "context-saveframe",
+ true,
+ "---",
+ null,
+ "context-printframe",
+ true,
+ "---",
+ null,
+ "context-take-frame-screenshot",
+ true,
+ "---",
+ null,
+ "context-viewframeinfo",
+ true,
+ ]),
+ null,
+ "---",
+ null,
+ "context-viewsource",
+ true,
+ ],
+ {
+ shiftkey: true,
+ }
+ );
+});
+
+add_task(async function test_textarea() {
+ // Disabled since this is seeing spell-check-enabled
+ // instead of spell-add-dictionaries-main
+ todo(false, "spell checker tests are failing, bug 1246296");
+
+ /*
+ yield test_contextmenu("#test-textarea",
+ ["context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null,
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-add-dictionaries-main", true,
+ ],
+ {
+ skipFocusChange: true,
+ }
+ );
+ */
+});
+
+add_task(async function test_textarea_spellcheck() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+
+ /*
+ yield test_contextmenu("#test-textarea",
+ ["*chubbiness", true, // spelling suggestion
+ "spell-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ],
+ {
+ waitForSpellCheck: true,
+ offsetX: 6,
+ offsetY: 6,
+ postCheckContextMenuFn() {
+ document.getElementById("spell-add-to-dictionary").doCommand();
+ }
+ }
+ );
+ */
+});
+
+add_task(async function test_plaintext2() {
+ await test_contextmenu("#test-text", plainTextItems);
+});
+
+add_task(async function test_undo_add_to_dictionary() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+
+ /*
+ yield test_contextmenu("#test-textarea",
+ ["spell-undo-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ],
+ {
+ waitForSpellCheck: true,
+ postCheckContextMenuFn() {
+ document.getElementById("spell-undo-add-to-dictionary")
+ .doCommand();
+ }
+ }
+ );
+ */
+});
+
+add_task(async function test_contenteditable() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+
+ /*
+ yield test_contextmenu("#test-contenteditable",
+ ["spell-no-suggestions", false,
+ "spell-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ],
+ {waitForSpellCheck: true}
+ );
+ */
+});
+
+add_task(async function test_copylinkcommand() {
+ await test_contextmenu("#test-link", null, {
+ async postCheckContextMenuFn() {
+ document.commandDispatcher
+ .getControllerForCommand("cmd_copyLink")
+ .doCommand("cmd_copyLink");
+
+ // The easiest way to check the clipboard is to paste the contents
+ // into a textbox.
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let doc = content.document;
+ let input = doc.getElementById("test-input");
+ input.focus();
+ input.value = "";
+ }
+ );
+ document.commandDispatcher
+ .getControllerForCommand("cmd_paste")
+ .doCommand("cmd_paste");
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let doc = content.document;
+ let input = doc.getElementById("test-input");
+ Assert.equal(
+ input.value,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://mozilla.com/",
+ "paste for command cmd_paste"
+ );
+ // Don't keep focus, because that may affect clipboard commands in
+ // subsequently-opened menus.
+ input.blur();
+ }
+ );
+ },
+ });
+});
+
+add_task(async function test_dom_full_screen() {
+ let fullscreenItems = NAVIGATION_ITEMS.concat([
+ "context-leave-dom-fullscreen",
+ true,
+ "---",
+ null,
+ "context-savepage",
+ true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "context-selectall",
+ true,
+ "---",
+ null,
+ "context-take-screenshot",
+ true,
+ "---",
+ null,
+ "context-viewsource",
+ true,
+ ]);
+ if (AppConstants.platform == "macosx") {
+ // Put the bookmarks item next to save page:
+ const bmPageIndex = fullscreenItems.indexOf("context-bookmarkpage");
+ let bmPageItems = fullscreenItems.splice(bmPageIndex, 2);
+ fullscreenItems.splice(
+ fullscreenItems.indexOf("context-savepage"),
+ 0,
+ ...bmPageItems
+ );
+ }
+ await test_contextmenu("#test-dom-full-screen", fullscreenItems, {
+ shiftkey: true,
+ async preCheckContextMenuFn() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ],
+ });
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let doc = content.document;
+ let win = doc.defaultView;
+ let full_screen_element = doc.getElementById("test-dom-full-screen");
+ let awaitFullScreenChange = ContentTaskUtils.waitForEvent(
+ win,
+ "fullscreenchange"
+ );
+ full_screen_element.requestFullscreen();
+ await awaitFullScreenChange;
+ }
+ );
+ },
+ async postCheckContextMenuFn() {
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let win = content.document.defaultView;
+ let awaitFullScreenChange = ContentTaskUtils.waitForEvent(
+ win,
+ "fullscreenchange"
+ );
+ content.document.exitFullscreen();
+ await awaitFullScreenChange;
+ }
+ );
+ },
+ });
+});
+
+add_task(async function test_pagemenu2() {
+ await test_contextmenu(
+ "#test-text",
+ [
+ ...NAVIGATION_ITEMS,
+ "context-savepage",
+ true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "context-selectall",
+ true,
+ "---",
+ null,
+ "context-take-screenshot",
+ true,
+ "---",
+ null,
+ "context-viewsource",
+ true,
+ ],
+ { shiftkey: true }
+ );
+});
+
+add_task(async function test_select_text() {
+ await test_contextmenu(
+ "#test-select-text",
+ [
+ "context-copy",
+ true,
+ "context-selectall",
+ true,
+ "context-print-selection",
+ true,
+ "---",
+ null,
+ "context-take-screenshot",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+ "---",
+ null,
+ "context-viewpartialsource-selection",
+ true,
+ ],
+ {
+ offsetX: 6,
+ offsetY: 6,
+ async preCheckContextMenuFn() {
+ await selectText("#test-select-text");
+ },
+ }
+ );
+});
+
+add_task(async function test_select_text_search_service_not_initialized() {
+ // Pretend the search service is not initialised.
+ Services.search.wrappedJSObject.forceInitializationStatusForTests(
+ "not initialized"
+ );
+ await test_contextmenu(
+ "#test-select-text",
+ [
+ "context-copy",
+ true,
+ "context-selectall",
+ true,
+ "context-print-selection",
+ true,
+ "---",
+ null,
+ "context-take-screenshot",
+ true,
+ "---",
+ null,
+ "context-viewpartialsource-selection",
+ true,
+ ],
+ {
+ offsetX: 6,
+ offsetY: 6,
+ async preCheckContextMenuFn() {
+ await selectText("#test-select-text");
+ },
+ }
+ );
+
+ // Restore the search service initialization status
+ Services.search.wrappedJSObject.forceInitializationStatusForTests("success");
+});
+
+add_task(async function test_select_text_link() {
+ await test_contextmenu(
+ "#test-select-text-link",
+ [
+ "context-openlinkincurrent",
+ true,
+ "context-openlinkintab",
+ true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink",
+ true,
+ "context-openlinkprivate",
+ true,
+ "---",
+ null,
+ "context-bookmarklink",
+ true,
+ "context-savelink",
+ true,
+ "---",
+ null,
+ "context-copy",
+ true,
+ "context-selectall",
+ true,
+ "context-print-selection",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+ "---",
+ null,
+ "context-viewpartialsource-selection",
+ true,
+ ],
+ {
+ offsetX: 6,
+ offsetY: 6,
+ async preCheckContextMenuFn() {
+ await selectText("#test-select-text-link");
+ },
+ async postCheckContextMenuFn() {
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let win = content.document.defaultView;
+ win.getSelection().removeAllRanges();
+ }
+ );
+ },
+ }
+ );
+});
+
+add_task(async function test_imagelink() {
+ await test_contextmenu("#test-image-link", [
+ "context-openlinkintab",
+ true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink",
+ true,
+ "context-openlinkprivate",
+ true,
+ "---",
+ null,
+ "context-bookmarklink",
+ true,
+ "context-savelink",
+ true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink",
+ true,
+ "---",
+ null,
+ "context-viewimage",
+ true,
+ "context-saveimage",
+ true,
+ "context-copyimage-contents",
+ true,
+ "context-copyimage",
+ true,
+ "context-sendimage",
+ true,
+ ...getTextRecognitionItems(),
+ ...(Services.prefs.getBoolPref("browser.menu.showViewImageInfo", false)
+ ? ["context-viewimageinfo", true]
+ : []),
+ "---",
+ null,
+ "context-setDesktopBackground",
+ true,
+ ]);
+});
+
+add_task(async function test_select_input_text() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+
+ /*
+ yield test_contextmenu("#test-select-input-text",
+ ["context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", true,
+ "context-selectall", true,
+ "---", null,
+ "context-searchselect", true,
+ "context-searchselect-private", true,
+ "---", null,
+ "spell-check-enabled", true
+ ].concat(LOGIN_FILL_ITEMS),
+ {
+ *preCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let win = doc.defaultView;
+ win.getSelection().removeAllRanges();
+ let element = doc.querySelector("#test-select-input-text");
+ element.select();
+ });
+ }
+ }
+ );
+ */
+});
+
+add_task(async function test_select_input_text_password() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+
+ /*
+ yield test_contextmenu("#test-select-input-text-type-password",
+ ["context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", true,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ // spell checker is shown on input[type="password"] on this testcase
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ].concat(LOGIN_FILL_ITEMS),
+ {
+ *preCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let win = doc.defaultView;
+ win.getSelection().removeAllRanges();
+ let element = doc.querySelector("#test-select-input-text-type-password");
+ element.select();
+ });
+ },
+ *postCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let win = content.document.defaultView;
+ win.getSelection().removeAllRanges();
+ });
+ }
+ }
+ );
+ */
+});
+
+add_task(async function test_longdesc() {
+ await test_contextmenu("#test-longdesc", [
+ "context-viewimage",
+ true,
+ "context-saveimage",
+ true,
+ "context-copyimage-contents",
+ true,
+ "context-copyimage",
+ true,
+ "context-sendimage",
+ true,
+ ...getTextRecognitionItems(),
+ ...(Services.prefs.getBoolPref("browser.menu.showViewImageInfo", false)
+ ? ["context-viewimageinfo", true]
+ : []),
+ "context-viewimagedesc",
+ true,
+ "---",
+ null,
+ "context-setDesktopBackground",
+ true,
+ ]);
+});
+
+add_task(async function test_srcdoc() {
+ await test_contextmenu("#test-srcdoc", [
+ ...NAVIGATION_ITEMS,
+ "context-savepage",
+ true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "context-selectall",
+ true,
+ "---",
+ null,
+ "frame",
+ null,
+ getThisFrameSubMenu([
+ "context-reloadframe",
+ true,
+ "---",
+ null,
+ "context-saveframe",
+ true,
+ "---",
+ null,
+ "context-printframe",
+ true,
+ "---",
+ null,
+ "context-take-frame-screenshot",
+ true,
+ "---",
+ null,
+ "context-viewframesource",
+ true,
+ "context-viewframeinfo",
+ true,
+ ]),
+ null,
+ "---",
+ null,
+ "context-viewsource",
+ true,
+ ]);
+});
+
+add_task(async function test_input_spell_false() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+
+ /*
+ yield test_contextmenu("#test-contenteditable-spellcheck-false",
+ ["context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "context-selectall", true,
+ ]
+ );
+ */
+});
+
+add_task(async function test_svg_link() {
+ await test_contextmenu("#svg-with-link > a", [
+ "context-openlinkintab",
+ true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink",
+ true,
+ "context-openlinkprivate",
+ true,
+ "---",
+ null,
+ "context-bookmarklink",
+ true,
+ "context-savelink",
+ true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+ ]);
+
+ await test_contextmenu("#svg-with-link2 > a", [
+ "context-openlinkintab",
+ true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink",
+ true,
+ "context-openlinkprivate",
+ true,
+ "---",
+ null,
+ "context-bookmarklink",
+ true,
+ "context-savelink",
+ true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+ ]);
+
+ await test_contextmenu("#svg-with-link3 > a", [
+ "context-openlinkintab",
+ true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink",
+ true,
+ "context-openlinkprivate",
+ true,
+ "---",
+ null,
+ "context-bookmarklink",
+ true,
+ "context-savelink",
+ true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+ ]);
+});
+
+add_task(async function test_svg_relative_link() {
+ await test_contextmenu("#svg-with-relative-link > a", [
+ "context-openlinkintab",
+ true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink",
+ true,
+ "context-openlinkprivate",
+ true,
+ "---",
+ null,
+ "context-bookmarklink",
+ true,
+ "context-savelink",
+ true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+ ]);
+
+ await test_contextmenu("#svg-with-relative-link2 > a", [
+ "context-openlinkintab",
+ true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink",
+ true,
+ "context-openlinkprivate",
+ true,
+ "---",
+ null,
+ "context-bookmarklink",
+ true,
+ "context-savelink",
+ true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+ ]);
+
+ await test_contextmenu("#svg-with-relative-link3 > a", [
+ "context-openlinkintab",
+ true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink",
+ true,
+ "context-openlinkprivate",
+ true,
+ "---",
+ null,
+ "context-bookmarklink",
+ true,
+ "context-savelink",
+ true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+ ]);
+});
+
+add_task(async function test_background_image() {
+ let bgImageItems = [
+ "context-viewimage",
+ true,
+ "context-copyimage",
+ true,
+ "context-sendimage",
+ true,
+ "---",
+ null,
+ "context-savepage",
+ true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "context-selectall",
+ true,
+ "---",
+ null,
+ "context-take-screenshot",
+ true,
+ "---",
+ null,
+ "context-viewsource",
+ true,
+ ];
+ if (AppConstants.platform == "macosx") {
+ // Back/fwd/(stop|reload) and their separator go before the image items,
+ // followed by the bookmark item which goes with save page - so we need
+ // to split up NAVIGATION_ITEMS and bgImageItems:
+ bgImageItems = [
+ ...NAVIGATION_ITEMS.slice(0, 8),
+ ...bgImageItems.slice(0, 8),
+ ...NAVIGATION_ITEMS.slice(8),
+ ...bgImageItems.slice(8),
+ ];
+ } else {
+ bgImageItems = NAVIGATION_ITEMS.concat(bgImageItems);
+ }
+ await test_contextmenu("#test-background-image", bgImageItems);
+
+ // Don't show image related context menu commands for links with background images.
+ await test_contextmenu("#test-background-image-link", [
+ "context-openlinkintab",
+ true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink",
+ true,
+ "context-openlinkprivate",
+ true,
+ "---",
+ null,
+ "context-bookmarklink",
+ true,
+ "context-savelink",
+ true,
+ "context-copylink",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+ ]);
+
+ // Don't show image related context menu commands when there is a selection
+ // with background images.
+ await test_contextmenu(
+ "#test-background-image",
+ [
+ "context-copy",
+ true,
+ "context-selectall",
+ true,
+ "context-print-selection",
+ true,
+ "---",
+ null,
+ "context-take-screenshot",
+ true,
+ "---",
+ null,
+ "context-searchselect",
+ true,
+ "context-searchselect-private",
+ true,
+ "---",
+ null,
+ "context-viewpartialsource-selection",
+ true,
+ ],
+ {
+ async preCheckContextMenuFn() {
+ await selectText("#test-background-image");
+ },
+ }
+ );
+});
+
+add_task(async function test_cleanup_html() {
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * Selects the text of the element that matches the provided `selector`
+ *
+ * @param {String} selector
+ * A selector passed to querySelector to find
+ * the element that will be referenced.
+ */
+async function selectText(selector) {
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [selector],
+ async function (contentSelector) {
+ info(`Selecting text of ${contentSelector}`);
+ let doc = content.document;
+ let win = doc.defaultView;
+ win.getSelection().removeAllRanges();
+ let div = doc.createRange();
+ let element = doc.querySelector(contentSelector);
+ Assert.ok(element, "Found element to select text from");
+ div.setStartBefore(element);
+ div.setEndAfter(element);
+ win.getSelection().addRange(div);
+ }
+ );
+}
+
+/**
+ * Not all platforms support text recognition.
+ * @returns {string[]}
+ */
+function getTextRecognitionItems() {
+ return Services.prefs.getBoolPref("dom.text-recognition.enabled") &&
+ Services.appinfo.isTextRecognitionSupported
+ ? ["context-imagetext", true]
+ : [];
+}
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_badiframe.js b/browser/base/content/test/contextMenu/browser_contextmenu_badiframe.js
new file mode 100644
index 0000000000..89e7fe15e0
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_badiframe.js
@@ -0,0 +1,182 @@
+/* Tests for proper behaviour of "Show this frame" context menu options with a valid frame and
+ a frame with an invalid url.
+ */
+
+// Two frames, one with text content, the other an error page
+var invalidPage = "http://127.0.0.1:55555/";
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+var validPage = "http://example.com/";
+var testPage =
+ 'data:text/html,<frameset cols="400,400"><frame src="' +
+ validPage +
+ '"><frame src="' +
+ invalidPage +
+ '"></frameset>';
+
+async function openTestPage() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank",
+ true,
+ true
+ );
+ let browser = tab.linkedBrowser;
+
+ // The test page has a top-level document and two subframes. One of
+ // those subframes is an error page, which doesn't fire a load event.
+ // We'll use BrowserTestUtils.browserLoaded and have it wait for all
+ // 3 loads before resolving.
+ let expectedLoads = 3;
+ let pageAndIframesLoaded = BrowserTestUtils.browserLoaded(
+ browser,
+ true /* includeSubFrames */,
+ url => {
+ expectedLoads--;
+ return !expectedLoads;
+ },
+ true /* maybeErrorPage */
+ );
+ BrowserTestUtils.loadURIString(browser, testPage);
+ await pageAndIframesLoaded;
+
+ // Make sure both the top-level document and the iframe documents have
+ // had a chance to present. We need this so that the context menu event
+ // gets dispatched properly.
+ for (let bc of [
+ ...browser.browsingContext.children,
+ browser.browsingContext,
+ ]) {
+ await SpecialPowers.spawn(bc, [], async function () {
+ await new Promise(resolve => {
+ content.requestAnimationFrame(resolve);
+ });
+ });
+ }
+}
+
+async function selectFromFrameMenu(frameNumber, menuId) {
+ const contextMenu = document.getElementById("contentAreaContextMenu");
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+
+ await BrowserTestUtils.synthesizeMouseAtPoint(
+ 40,
+ 40,
+ {
+ type: "contextmenu",
+ button: 2,
+ },
+ gBrowser.selectedBrowser.browsingContext.children[frameNumber]
+ );
+
+ await popupShownPromise;
+
+ let frameItem = document.getElementById("frame");
+ let framePopup = frameItem.menupopup;
+ let subPopupShownPromise = BrowserTestUtils.waitForEvent(
+ framePopup,
+ "popupshown"
+ );
+
+ frameItem.openMenu(true);
+ await subPopupShownPromise;
+
+ let subPopupHiddenPromise = BrowserTestUtils.waitForEvent(
+ framePopup,
+ "popuphidden"
+ );
+ let contextMenuHiddenPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ contextMenu.activateItem(document.getElementById(menuId));
+ await subPopupHiddenPromise;
+ await contextMenuHiddenPromise;
+}
+
+add_task(async function testOpenFrame() {
+ for (let frameNumber = 0; frameNumber < 2; frameNumber++) {
+ await openTestPage();
+
+ let expectedResultURI = [validPage, invalidPage][frameNumber];
+
+ info("show only this frame for " + expectedResultURI);
+
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ expectedResultURI,
+ frameNumber == 1
+ );
+
+ await selectFromFrameMenu(frameNumber, "context-showonlythisframe");
+ await browserLoadedPromise;
+
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ expectedResultURI,
+ "Should navigate to page url, not about:neterror"
+ );
+
+ gBrowser.removeCurrentTab();
+ }
+});
+
+add_task(async function testOpenFrameInTab() {
+ for (let frameNumber = 0; frameNumber < 2; frameNumber++) {
+ await openTestPage();
+
+ let expectedResultURI = [validPage, invalidPage][frameNumber];
+
+ info("open frame in tab for " + expectedResultURI);
+
+ let newTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ expectedResultURI,
+ false
+ );
+ await selectFromFrameMenu(frameNumber, "context-openframeintab");
+ let newTab = await newTabPromise;
+
+ await BrowserTestUtils.switchTab(gBrowser, newTab);
+
+ // We should now have the error page in a new, active tab.
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ expectedResultURI,
+ "New tab should have page url, not about:neterror"
+ );
+
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+ }
+});
+
+add_task(async function testOpenFrameInWindow() {
+ for (let frameNumber = 0; frameNumber < 2; frameNumber++) {
+ await openTestPage();
+
+ let expectedResultURI = [validPage, invalidPage][frameNumber];
+
+ info("open frame in window for " + expectedResultURI);
+
+ let newWindowPromise = BrowserTestUtils.waitForNewWindow({
+ url: frameNumber == 1 ? invalidPage : validPage,
+ maybeErrorPage: frameNumber == 1,
+ });
+ await selectFromFrameMenu(frameNumber, "context-openframe");
+ let newWindow = await newWindowPromise;
+
+ is(
+ newWindow.gBrowser.selectedBrowser.currentURI.spec,
+ expectedResultURI,
+ "New window should have page url, not about:neterror"
+ );
+
+ newWindow.close();
+ gBrowser.removeCurrentTab();
+ }
+});
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_contenteditable.js b/browser/base/content/test/contextMenu/browser_contextmenu_contenteditable.js
new file mode 100644
index 0000000000..ccb0be8d95
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_contenteditable.js
@@ -0,0 +1,118 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let contextMenu;
+
+const example_base =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/browser/browser/base/content/test/contextMenu/";
+const chrome_base =
+ "chrome://mochitests/content/browser/browser/base/content/test/contextMenu/";
+
+/* import-globals-from contextmenu_common.js */
+Services.scriptloader.loadSubScript(
+ chrome_base + "contextmenu_common.js",
+ this
+);
+
+async function openMenuAndPaste(browser, useFormatting) {
+ const kElementToUse = "test-contenteditable-spellcheck-false";
+ let oldText = await SpecialPowers.spawn(browser, [kElementToUse], elemID => {
+ return content.document.getElementById(elemID).textContent;
+ });
+
+ // Open context menu and paste
+ await test_contextmenu(
+ "#" + kElementToUse,
+ [
+ "context-undo",
+ null, // whether we can undo changes mid-test.
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ false,
+ "context-copy",
+ false,
+ "context-paste",
+ true,
+ "context-paste-no-formatting",
+ true,
+ "context-delete",
+ false,
+ "context-selectall",
+ true,
+ ],
+ {
+ keepMenuOpen: true,
+ }
+ );
+ let popupHidden = BrowserTestUtils.waitForPopupEvent(contextMenu, "hidden");
+ let menuID = "context-paste" + (useFormatting ? "" : "-no-formatting");
+ contextMenu.activateItem(document.getElementById(menuID));
+ await popupHidden;
+ await SpecialPowers.spawn(
+ browser,
+ [kElementToUse, oldText, useFormatting],
+ (elemID, textToReset, expectStrong) => {
+ let node = content.document.getElementById(elemID);
+ Assert.stringContains(
+ node.textContent,
+ "Bold text",
+ "Text should have been pasted"
+ );
+ if (expectStrong) {
+ isnot(
+ node.querySelector("strong"),
+ null,
+ "Should be markup in the text."
+ );
+ } else {
+ is(
+ node.querySelector("strong"),
+ null,
+ "Should be no markup in the text."
+ );
+ }
+ node.textContent = textToReset;
+ }
+ );
+}
+
+add_task(async function test_contenteditable() {
+ // Put some HTML on the clipboard:
+ const xferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(
+ Ci.nsITransferable
+ );
+ xferable.init(null);
+ xferable.addDataFlavor("text/html");
+ xferable.setTransferData(
+ "text/html",
+ PlacesUtils.toISupportsString("<strong>Bold text</strong>")
+ );
+ xferable.addDataFlavor("text/plain");
+ xferable.setTransferData(
+ "text/plain",
+ PlacesUtils.toISupportsString("Bold text")
+ );
+ Services.clipboard.setData(
+ xferable,
+ null,
+ Services.clipboard.kGlobalClipboard
+ );
+
+ let url = example_base + "subtst_contextmenu.html";
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ await openMenuAndPaste(browser, false);
+ await openMenuAndPaste(browser, true);
+ }
+ );
+});
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_iframe.js b/browser/base/content/test/contextMenu/browser_contextmenu_iframe.js
new file mode 100644
index 0000000000..bd52862eb4
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_iframe.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_LINK = "https://example.com/";
+const RESOURCE_LINK =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "test_contextmenu_iframe.html";
+
+/* This test checks that a context menu can open up
+ * a frame into it's own tab. */
+
+add_task(async function test_open_iframe() {
+ let testTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ RESOURCE_LINK
+ );
+ const selector = "#iframe";
+ const openPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ TEST_LINK,
+ false
+ );
+ const contextMenu = document.getElementById("contentAreaContextMenu");
+ is(contextMenu.state, "closed", "checking if popup is closed");
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ selector,
+ {
+ type: "contextmenu",
+ button: 2,
+ centered: true,
+ },
+ gBrowser.selectedBrowser
+ );
+ await awaitPopupShown;
+ info("Popup Shown");
+ const awaitPopupHidden = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+
+ // Open frame submenu
+ const frameItem = contextMenu.querySelector("#frame");
+ const menuPopup = frameItem.menupopup;
+ const menuPopupPromise = BrowserTestUtils.waitForEvent(
+ menuPopup,
+ "popupshown"
+ );
+ frameItem.openMenu(true);
+ await menuPopupPromise;
+
+ let domItem = contextMenu.querySelector("#context-openframeintab");
+ info("Going to click item " + domItem.id);
+ ok(
+ BrowserTestUtils.is_visible(domItem),
+ "DOM context menu item tab should be visible"
+ );
+ ok(!domItem.disabled, "DOM context menu item tab shouldn't be disabled");
+ contextMenu.activateItem(domItem);
+
+ let openedTab = await openPromise;
+ await awaitPopupHidden;
+ await BrowserTestUtils.removeTab(openedTab);
+
+ BrowserTestUtils.removeTab(testTab);
+});
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_input.js b/browser/base/content/test/contextMenu/browser_contextmenu_input.js
new file mode 100644
index 0000000000..c580a8184a
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_input.js
@@ -0,0 +1,387 @@
+"use strict";
+
+let contextMenu;
+let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
+
+const NAVIGATION_ITEMS =
+ AppConstants.platform == "macosx"
+ ? [
+ "context-back",
+ false,
+ "context-forward",
+ false,
+ "context-reload",
+ true,
+ "---",
+ null,
+ "context-bookmarkpage",
+ true,
+ ]
+ : [
+ "context-navigation",
+ null,
+ [
+ "context-back",
+ false,
+ "context-forward",
+ false,
+ "context-reload",
+ true,
+ "context-bookmarkpage",
+ true,
+ ],
+ null,
+ "---",
+ null,
+ ];
+
+add_task(async function test_setup() {
+ const example_base =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/browser/browser/base/content/test/contextMenu/";
+ const url = example_base + "subtst_contextmenu_input.html";
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ const chrome_base =
+ "chrome://mochitests/content/browser/browser/base/content/test/contextMenu/";
+ const contextmenu_common = chrome_base + "contextmenu_common.js";
+ /* import-globals-from contextmenu_common.js */
+ Services.scriptloader.loadSubScript(contextmenu_common, this);
+});
+
+add_task(async function test_text_input() {
+ await test_contextmenu("#input_text", [
+ "context-undo",
+ false,
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ false,
+ "context-copy",
+ false,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ false,
+ "context-selectall",
+ false,
+ "---",
+ null,
+ "spell-check-enabled",
+ true,
+ ]);
+});
+
+add_task(async function test_text_input_disabled() {
+ await test_contextmenu(
+ "#input_disabled",
+ [
+ "context-undo",
+ false,
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ false,
+ "context-copy",
+ false,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ false,
+ "context-selectall",
+ false,
+ "---",
+ null,
+ "spell-check-enabled",
+ true,
+ ],
+ { skipFocusChange: true }
+ );
+});
+
+add_task(async function test_password_input() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["signon.generation.enabled", false],
+ ["layout.forms.reveal-password-context-menu.enabled", true],
+ ],
+ });
+ todo(
+ false,
+ "context-selectall is enabled on osx-e10s, and windows when" +
+ " it should be disabled"
+ );
+ await test_contextmenu(
+ "#input_password",
+ [
+ "manage-saved-logins",
+ true,
+ "---",
+ null,
+ "context-undo",
+ false,
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ false,
+ "context-copy",
+ false,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ false,
+ "context-selectall",
+ null,
+ "context-reveal-password",
+ null,
+ ],
+ {
+ skipFocusChange: true,
+ // Need to dynamically add the "password" type or LoginManager
+ // will think that the form inputs on the page are part of a login form
+ // and will add fill-login context menu items. The element needs to be
+ // re-created as type=text afterwards since it uses hasBeenTypePassword.
+ async preCheckContextMenuFn() {
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let doc = content.document;
+ let input = doc.getElementById("input_password");
+ input.type = "password";
+ input.clientTop; // force layout flush
+ }
+ );
+ },
+ async postCheckContextMenuFn() {
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let doc = content.document;
+ let input = doc.getElementById("input_password");
+ input.outerHTML = `<input id=\"input_password\">`;
+ input.clientTop; // force layout flush
+ }
+ );
+ },
+ }
+ );
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function firefox_relay_input() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["signon.firefoxRelay.feature", "enabled"]],
+ });
+
+ await test_contextmenu("#input_username", [
+ "use-relay-mask",
+ true,
+ "---",
+ null,
+ "context-undo",
+ false,
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ false,
+ "context-copy",
+ false,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ false,
+ "context-selectall",
+ false,
+ "---",
+ null,
+ "spell-check-enabled",
+ true,
+ ]);
+
+ await test_contextmenu(
+ "#input_email",
+ [
+ "use-relay-mask",
+ true,
+ "---",
+ null,
+ "context-undo",
+ false,
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ false,
+ "context-copy",
+ false,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ false,
+ "context-selectall",
+ null,
+ ],
+ {
+ skipFocusChange: true,
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_tel_email_url_number_input() {
+ todo(
+ false,
+ "context-selectall is enabled on osx-e10s, and windows when" +
+ " it should be disabled"
+ );
+ for (let selector of [
+ "#input_email",
+ "#input_url",
+ "#input_tel",
+ "#input_number",
+ ]) {
+ await test_contextmenu(
+ selector,
+ [
+ "context-undo",
+ false,
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ false,
+ "context-copy",
+ false,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ false,
+ "context-selectall",
+ null,
+ ],
+ {
+ skipFocusChange: true,
+ }
+ );
+ }
+});
+
+add_task(
+ async function test_date_time_color_range_month_week_datetimelocal_input() {
+ for (let selector of [
+ "#input_date",
+ "#input_time",
+ "#input_color",
+ "#input_range",
+ "#input_month",
+ "#input_week",
+ "#input_datetime-local",
+ ]) {
+ await test_contextmenu(
+ selector,
+ [
+ ...NAVIGATION_ITEMS,
+ "context-savepage",
+ true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "context-selectall",
+ null,
+ "---",
+ null,
+ "context-viewsource",
+ true,
+ ],
+ {
+ skipFocusChange: true,
+ }
+ );
+ }
+ }
+);
+
+add_task(async function test_search_input() {
+ todo(
+ false,
+ "context-selectall is enabled on osx-e10s, and windows when" +
+ " it should be disabled"
+ );
+ await test_contextmenu(
+ "#input_search",
+ [
+ "context-undo",
+ false,
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ false,
+ "context-copy",
+ false,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ false,
+ "context-selectall",
+ null,
+ "---",
+ null,
+ "spell-check-enabled",
+ true,
+ ],
+ { skipFocusChange: true }
+ );
+});
+
+add_task(async function test_text_input_readonly() {
+ todo(
+ false,
+ "context-selectall is enabled on osx-e10s, and windows when" +
+ " it should be disabled"
+ );
+ todo(
+ false,
+ "spell-check should not be enabled for input[readonly]. see bug 1246296"
+ );
+ await test_contextmenu(
+ "#input_readonly",
+ [
+ "context-undo",
+ false,
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ false,
+ "context-copy",
+ false,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ false,
+ "context-selectall",
+ null,
+ ],
+ {
+ skipFocusChange: true,
+ }
+ );
+});
+
+add_task(async function test_cleanup() {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_inspect.js b/browser/base/content/test/contextMenu/browser_contextmenu_inspect.js
new file mode 100644
index 0000000000..94241e9e1f
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_inspect.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that we show the inspect item(s) as appropriate.
+ */
+add_task(async function test_contextmenu_inspect() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["devtools.selfxss.count", 0],
+ ["devtools.everOpened", false],
+ ],
+ });
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ for (let [pref, value, expectation] of [
+ ["devtools.selfxss.count", 10, true],
+ ["devtools.selfxss.count", 0, false],
+ ["devtools.everOpened", false, false],
+ ["devtools.everOpened", true, true],
+ ]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["devtools.selfxss.count", value]],
+ });
+ is(contextMenu.state, "closed", "checking if popup is closed");
+ let promisePopupShown = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ let promisePopupHidden = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ await BrowserTestUtils.synthesizeMouse(
+ "body",
+ 2,
+ 2,
+ { type: "contextmenu", button: 2 },
+ browser
+ );
+ await promisePopupShown;
+ let inspectItem = document.getElementById("context-inspect");
+ ok(
+ !inspectItem.hidden,
+ `Inspect should be shown (pref ${pref} is ${value}).`
+ );
+ let inspectA11y = document.getElementById("context-inspect-a11y");
+ is(
+ inspectA11y.hidden,
+ !expectation,
+ `A11y should be ${
+ expectation ? "visible" : "hidden"
+ } (pref ${pref} is ${value}).`
+ );
+ contextMenu.hidePopup();
+ await promisePopupHidden;
+ }
+ });
+});
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_keyword.js b/browser/base/content/test/contextMenu/browser_contextmenu_keyword.js
new file mode 100644
index 0000000000..2e1253107c
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_keyword.js
@@ -0,0 +1,198 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let contextMenu;
+
+const example_base =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/browser/browser/base/content/test/contextMenu/";
+const MAIN_URL = example_base + "subtst_contextmenu_keyword.html";
+
+add_task(async function test_setup() {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, MAIN_URL);
+
+ const chrome_base =
+ "chrome://mochitests/content/browser/browser/base/content/test/contextMenu/";
+ const contextmenu_common = chrome_base + "contextmenu_common.js";
+ /* import-globals-from contextmenu_common.js */
+ Services.scriptloader.loadSubScript(contextmenu_common, this);
+});
+
+add_task(async function test_text_input_spellcheck_noform() {
+ await test_contextmenu(
+ "#input_text_no_form",
+ [
+ "context-undo",
+ false,
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ null, // ignore the enabled/disabled states; there are race conditions
+ // in the edit commands but they're not relevant for what we're testing.
+ "context-copy",
+ null,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ null,
+ "context-selectall",
+ null,
+ "---",
+ null,
+ "spell-check-enabled",
+ true,
+ "spell-dictionaries",
+ true,
+ [
+ "spell-check-dictionary-en-US",
+ true,
+ "---",
+ null,
+ "spell-add-dictionaries",
+ true,
+ ],
+ null,
+ ],
+ {
+ waitForSpellCheck: true,
+ async preCheckContextMenuFn() {
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let doc = content.document;
+ let input = doc.getElementById("input_text_no_form");
+ input.setAttribute("spellcheck", "true");
+ input.clientTop; // force layout flush
+ }
+ );
+ },
+ }
+ );
+});
+
+add_task(async function test_text_input_spellcheck_loginform() {
+ await test_contextmenu(
+ "#login_text",
+ [
+ "manage-saved-logins",
+ true,
+ "---",
+ null,
+ "context-undo",
+ false,
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ null, // ignore the enabled/disabled states; there are race conditions
+ // in the edit commands but they're not relevant for what we're testing.
+ "context-copy",
+ null,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ null,
+ "context-selectall",
+ null,
+ "---",
+ null,
+ "spell-check-enabled",
+ true,
+ "spell-dictionaries",
+ true,
+ [
+ "spell-check-dictionary-en-US",
+ true,
+ "---",
+ null,
+ "spell-add-dictionaries",
+ true,
+ ],
+ null,
+ ],
+ {
+ waitForSpellCheck: true,
+ async preCheckContextMenuFn() {
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let doc = content.document;
+ let input = doc.getElementById("login_text");
+ input.setAttribute("spellcheck", "true");
+ input.clientTop; // force layout flush
+ }
+ );
+ },
+ }
+ );
+});
+
+add_task(async function test_text_input_spellcheck_searchform() {
+ await test_contextmenu(
+ "#search_text",
+ [
+ "context-undo",
+ false,
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ null, // ignore the enabled/disabled states; there are race conditions
+ // in the edit commands but they're not relevant for what we're testing.
+ "context-copy",
+ null,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ null,
+ "context-selectall",
+ null,
+ "---",
+ null,
+ "context-keywordfield",
+ null,
+ "---",
+ null,
+ "spell-check-enabled",
+ true,
+ "spell-dictionaries",
+ true,
+ [
+ "spell-check-dictionary-en-US",
+ true,
+ "---",
+ null,
+ "spell-add-dictionaries",
+ true,
+ ],
+ null,
+ ],
+ {
+ waitForSpellCheck: true,
+ async preCheckContextMenuFn() {
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let doc = content.document;
+ let input = doc.getElementById("search_text");
+ input.setAttribute("spellcheck", "true");
+ input.clientTop; // force layout flush
+ }
+ );
+ },
+ }
+ );
+});
+
+add_task(async function test_cleanup() {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_linkopen.js b/browser/base/content/test/contextMenu/browser_contextmenu_linkopen.js
new file mode 100644
index 0000000000..ac793b8011
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_linkopen.js
@@ -0,0 +1,109 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_LINK = "https://example.com/";
+const RESOURCE_LINK =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "test_contextmenu_links.html";
+
+async function activateContextAndWaitFor(selector, where) {
+ info("Starting test for " + where);
+ let contextMenuItem = "openlink";
+ let openPromise;
+ let closeMethod;
+ switch (where) {
+ case "tab":
+ contextMenuItem += "intab";
+ openPromise = BrowserTestUtils.waitForNewTab(gBrowser, TEST_LINK, false);
+ closeMethod = async tab => BrowserTestUtils.removeTab(tab);
+ break;
+ case "privatewindow":
+ contextMenuItem += "private";
+ openPromise = BrowserTestUtils.waitForNewWindow({ url: TEST_LINK }).then(
+ win => {
+ ok(
+ PrivateBrowsingUtils.isWindowPrivate(win),
+ "Should have opened a private window."
+ );
+ return win;
+ }
+ );
+ closeMethod = async win => BrowserTestUtils.closeWindow(win);
+ break;
+ case "window":
+ // No contextMenuItem suffix for normal new windows;
+ openPromise = BrowserTestUtils.waitForNewWindow({ url: TEST_LINK }).then(
+ win => {
+ ok(
+ !PrivateBrowsingUtils.isWindowPrivate(win),
+ "Should have opened a normal window."
+ );
+ return win;
+ }
+ );
+ closeMethod = async win => BrowserTestUtils.closeWindow(win);
+ break;
+ }
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ is(contextMenu.state, "closed", "checking if popup is closed");
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ await BrowserTestUtils.synthesizeMouse(
+ selector,
+ 0,
+ 0,
+ {
+ type: "contextmenu",
+ button: 2,
+ centered: true,
+ },
+ gBrowser.selectedBrowser
+ );
+ await awaitPopupShown;
+ info("Popup Shown");
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ let domItem = contextMenu.querySelector("#context-" + contextMenuItem);
+ info("Going to click item " + domItem.id);
+ ok(
+ BrowserTestUtils.is_visible(domItem),
+ "DOM context menu item " + where + " should be visible"
+ );
+ ok(
+ !domItem.disabled,
+ "DOM context menu item " + where + " shouldn't be disabled"
+ );
+ contextMenu.activateItem(domItem);
+ await awaitPopupHidden;
+
+ info("Waiting for the link to open");
+ let openedThing = await openPromise;
+ info("Waiting for the opened window/tab to close");
+ await closeMethod(openedThing);
+}
+
+add_task(async function test_select_text_link() {
+ let testTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ RESOURCE_LINK
+ );
+ for (let elementID of [
+ "test-link",
+ "test-image-link",
+ "svg-with-link",
+ "svg-with-relative-link",
+ ]) {
+ for (let where of ["tab", "window", "privatewindow"]) {
+ await activateContextAndWaitFor("#" + elementID, where);
+ }
+ }
+ BrowserTestUtils.removeTab(testTab);
+});
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_loadblobinnewtab.html b/browser/base/content/test/contextMenu/browser_contextmenu_loadblobinnewtab.html
new file mode 100644
index 0000000000..ca96fcfaa0
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_loadblobinnewtab.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8" />
+</head>
+
+<body onload="add_content()">
+ <p>This example creates a typed array containing the ASCII codes for the space character through the letter Z, then
+ converts it to an object URL.A link to open that object URL is created. Click the link to see the decoded object
+ URL.</p>
+ <br />
+ <br />
+ <a id='blob-url-link'>Open the array URL</a>
+ <br />
+ <br />
+ <a id='blob-url-referrer-link'>Open the URL that fetches the URL above</a>
+
+ <script>
+ function typedArrayToURL(typedArray, mimeType) {
+ return URL.createObjectURL(new Blob([typedArray.buffer], { type: mimeType }))
+ }
+
+ function add_content() {
+ const bytes = new Uint8Array(59);
+
+ for (let i = 0;i < 59;i++) {
+ bytes[i] = 32 + i;
+ }
+
+ const url = typedArrayToURL(bytes, 'text/plain');
+ document.getElementById('blob-url-link').href = url;
+
+ const ref_url = URL.createObjectURL(new Blob([`
+ <body>
+ <script>
+ fetch("${url}", {headers: {'Content-Type': 'text/plain'}})
+ .then((response) => {
+ response.text().then((textData) => {
+ var pre = document.createElement("pre");
+ pre.textContent = textData.trim();
+ document.body.insertBefore(pre, document.body.firstChild);
+ });
+ });
+ <\/script>
+ <\/body>
+ `], { type: 'text/html' }));
+
+ document.getElementById('blob-url-referrer-link').href = ref_url;
+ };
+
+ </script>
+
+</body>
+
+</html>
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_loadblobinnewtab.js b/browser/base/content/test/contextMenu/browser_contextmenu_loadblobinnewtab.js
new file mode 100644
index 0000000000..cbf1b27590
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_loadblobinnewtab.js
@@ -0,0 +1,186 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const RESOURCE_LINK =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "browser_contextmenu_loadblobinnewtab.html";
+
+const blobDataAsString = `!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
+
+// Helper method to right click on the provided link (selector as id),
+// open in new tab and return the content of the first <pre> under the
+// <body> of the new tab's document.
+async function rightClickOpenInNewTabAndReturnContent(selector) {
+ const loaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ RESOURCE_LINK
+ );
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, RESOURCE_LINK);
+ await loaded;
+
+ const generatedBlobURL = await ContentTask.spawn(
+ gBrowser.selectedBrowser,
+ { selector },
+ async args => {
+ return content.document.getElementById(args.selector).href;
+ }
+ );
+
+ const contextMenu = document.getElementById("contentAreaContextMenu");
+ is(contextMenu.state, "closed", "checking if context menu is closed");
+
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#" + selector,
+ { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser
+ );
+ await awaitPopupShown;
+
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+
+ const openPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ generatedBlobURL,
+ false
+ );
+
+ document.getElementById("context-openlinkintab").doCommand();
+
+ contextMenu.hidePopup();
+ await awaitPopupHidden;
+
+ let openTab = await openPromise;
+
+ await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[1]);
+
+ let blobDataFromContent = await ContentTask.spawn(
+ gBrowser.selectedBrowser,
+ null,
+ async function () {
+ while (!content.document.querySelector("body pre")) {
+ await new Promise(resolve =>
+ content.setTimeout(() => {
+ resolve();
+ }, 100)
+ );
+ }
+ return content.document.body.firstElementChild.innerText.trim();
+ }
+ );
+
+ let tabClosed = BrowserTestUtils.waitForTabClosing(openTab);
+ await BrowserTestUtils.removeTab(openTab);
+ await tabClosed;
+
+ return blobDataFromContent;
+}
+
+// Helper method to open selected link in new tab (selector as id),
+// and return the content of the first <pre> under the <body> of
+// the new tab's document.
+async function openInNewTabAndReturnContent(selector) {
+ const loaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ RESOURCE_LINK
+ );
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, RESOURCE_LINK);
+ await loaded;
+
+ const generatedBlobURL = await ContentTask.spawn(
+ gBrowser.selectedBrowser,
+ { selector },
+ async args => {
+ return content.document.getElementById(args.selector).href;
+ }
+ );
+
+ let openTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ generatedBlobURL
+ );
+
+ let blobDataFromContent = await ContentTask.spawn(
+ gBrowser.selectedBrowser,
+ null,
+ async function () {
+ while (!content.document.querySelector("body pre")) {
+ await new Promise(resolve =>
+ content.setTimeout(() => {
+ resolve();
+ }, 100)
+ );
+ }
+ return content.document.body.firstElementChild.innerText.trim();
+ }
+ );
+
+ let tabClosed = BrowserTestUtils.waitForTabClosing(openTab);
+ await BrowserTestUtils.removeTab(openTab);
+ await tabClosed;
+
+ return blobDataFromContent;
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.bloburl_per_agent_cluster", false]],
+ });
+});
+
+add_task(async function test_rightclick_open_bloburl_in_new_tab() {
+ let blobDataFromLoadedPage = await rightClickOpenInNewTabAndReturnContent(
+ "blob-url-link"
+ );
+ is(
+ blobDataFromLoadedPage,
+ blobDataAsString,
+ "The blobURL is correctly loaded"
+ );
+});
+
+add_task(async function test_rightclick_open_bloburl_referrer_in_new_tab() {
+ let blobDataFromLoadedPage = await rightClickOpenInNewTabAndReturnContent(
+ "blob-url-referrer-link"
+ );
+ is(
+ blobDataFromLoadedPage,
+ blobDataAsString,
+ "The blobURL is correctly loaded"
+ );
+});
+
+add_task(async function test_open_bloburl_in_new_tab() {
+ let blobDataFromLoadedPage = await openInNewTabAndReturnContent(
+ "blob-url-link"
+ );
+ is(
+ blobDataFromLoadedPage,
+ blobDataAsString,
+ "The blobURL is correctly loaded"
+ );
+});
+
+add_task(async function test_open_bloburl_referrer_in_new_tab() {
+ let blobDataFromLoadedPage = await openInNewTabAndReturnContent(
+ "blob-url-referrer-link"
+ );
+ is(
+ blobDataFromLoadedPage,
+ blobDataAsString,
+ "The blobURL is correctly loaded"
+ );
+});
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_save_blocked.js b/browser/base/content/test/contextMenu/browser_contextmenu_save_blocked.js
new file mode 100644
index 0000000000..5064d9a316
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_save_blocked.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+function mockPromptService() {
+ let { prompt } = Services;
+ let promptService = {
+ QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]),
+ alert: () => {},
+ };
+ Services.prompt = promptService;
+ registerCleanupFunction(() => {
+ Services.prompt = prompt;
+ });
+ return promptService;
+}
+
+add_task(async function test_save_link_blocked_by_extension() {
+ let ext = ExtensionTestUtils.loadExtension({
+ manifest: {
+ browser_specific_settings: { gecko: { id: "cancel@test" } },
+ name: "Cancel Test",
+ permissions: ["webRequest", "webRequestBlocking", "<all_urls>"],
+ },
+
+ background() {
+ // eslint-disable-next-line no-undef
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ return { cancel: details.url === "http://example.com/" };
+ },
+ { urls: ["*://*/*"] },
+ ["blocking"]
+ );
+ },
+ });
+ await ext.startup();
+
+ await BrowserTestUtils.withNewTab(
+ `data:text/html;charset=utf-8,<a href="http://example.com">Download</a>`,
+ async browser => {
+ let menu = document.getElementById("contentAreaContextMenu");
+ let popupShown = BrowserTestUtils.waitForEvent(menu, "popupshown");
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "a[href]",
+ { type: "contextmenu", button: 2 },
+ browser
+ );
+ await popupShown;
+
+ await new Promise(resolve => {
+ let promptService = mockPromptService();
+ promptService.alert = (window, title, msg) => {
+ is(
+ msg,
+ "The download cannot be saved because it is blocked by Cancel Test.",
+ "prompt should be shown"
+ );
+ setTimeout(resolve, 0);
+ };
+
+ MockFilePicker.showCallback = function (fp) {
+ ok(false, "filepicker should never been shown");
+ setTimeout(resolve, 0);
+ return Ci.nsIFilePicker.returnCancel;
+ };
+ menu.activateItem(menu.querySelector("#context-savelink"));
+ });
+ }
+ );
+
+ await ext.unload();
+});
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_share_macosx.js b/browser/base/content/test/contextMenu/browser_contextmenu_share_macosx.js
new file mode 100644
index 0000000000..8175b93052
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_share_macosx.js
@@ -0,0 +1,144 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+const BASE = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+);
+const TEST_URL = BASE + "browser_contextmenu_shareurl.html";
+
+let mockShareData = [
+ {
+ name: "Test",
+ menuItemTitle: "Sharing Service Test",
+ image:
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKE" +
+ "lEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==",
+ },
+];
+
+// Setup spies for observing function calls from MacSharingService
+let shareUrlSpy = sinon.spy();
+let openSharingPreferencesSpy = sinon.spy();
+let getSharingProvidersSpy = sinon.spy();
+
+let stub = sinon.stub(gBrowser, "MacSharingService").get(() => {
+ return {
+ getSharingProviders(url) {
+ getSharingProvidersSpy(url);
+ return mockShareData;
+ },
+ shareUrl(name, url, title) {
+ shareUrlSpy(name, url, title);
+ },
+ openSharingPreferences() {
+ openSharingPreferencesSpy();
+ },
+ };
+});
+
+registerCleanupFunction(async function () {
+ stub.restore();
+});
+
+/**
+ * Test the "Share" item menus in the tab contextmenu on MacOSX.
+ */
+add_task(async function test_contextmenu_share_macosx() {
+ await BrowserTestUtils.withNewTab(TEST_URL, async () => {
+ let contextMenu = await openTabContextMenu(gBrowser.selectedTab);
+ await BrowserTestUtils.waitForMutationCondition(
+ contextMenu,
+ { childList: true },
+ () => contextMenu.querySelector(".share-tab-url-item")
+ );
+ ok(true, "Got Share item");
+
+ await openMenuPopup(contextMenu);
+ ok(getSharingProvidersSpy.calledOnce, "getSharingProviders called");
+
+ info(
+ "Check we have a service and one extra menu item for the More... button"
+ );
+ let popup = contextMenu.querySelector(".share-tab-url-item").menupopup;
+ let items = popup.querySelectorAll("menuitem");
+ is(items.length, 2, "There should be 2 sharing services.");
+
+ info("Click on the sharing service");
+ let menuPopupClosedPromised = BrowserTestUtils.waitForPopupEvent(
+ contextMenu,
+ "hidden"
+ );
+ let shareButton = items[0];
+ is(
+ shareButton.label,
+ mockShareData[0].menuItemTitle,
+ "Share button's label should match the service's menu item title. "
+ );
+ is(
+ shareButton.getAttribute("share-name"),
+ mockShareData[0].name,
+ "Share button's share-name value should match the service's name. "
+ );
+
+ popup.activateItem(shareButton);
+ await menuPopupClosedPromised;
+
+ ok(shareUrlSpy.calledOnce, "shareUrl called");
+
+ info("Check the correct data was shared.");
+ let [name, url, title] = shareUrlSpy.getCall(0).args;
+ is(name, mockShareData[0].name, "Shared correct service name");
+ is(url, TEST_URL, "Shared correct URL");
+ is(title, "Sharing URL", "Shared the correct title.");
+
+ info("Test the More... button");
+ contextMenu = await openTabContextMenu(gBrowser.selectedTab);
+ await openMenuPopup(contextMenu);
+ // Since the tab context menu was collapsed previously, the popup needs to get the
+ // providers again.
+ ok(getSharingProvidersSpy.calledTwice, "getSharingProviders called again");
+ popup = contextMenu.querySelector(".share-tab-url-item").menupopup;
+ items = popup.querySelectorAll("menuitem");
+ is(items.length, 2, "There should be 2 sharing services.");
+
+ info("Click on the More Button");
+ let moreButton = items[1];
+ menuPopupClosedPromised = BrowserTestUtils.waitForPopupEvent(
+ contextMenu,
+ "hidden"
+ );
+ popup.activateItem(moreButton);
+ await menuPopupClosedPromised;
+ ok(openSharingPreferencesSpy.calledOnce, "openSharingPreferences called");
+ });
+});
+
+/**
+ * Helper for opening the toolbar context menu.
+ */
+async function openTabContextMenu(tab) {
+ info("Opening tab context menu");
+ let contextMenu = document.getElementById("tabContextMenu");
+ let openTabContextMenuPromise = BrowserTestUtils.waitForPopupEvent(
+ contextMenu,
+ "shown"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(tab, { type: "contextmenu" });
+ await openTabContextMenuPromise;
+ return contextMenu;
+}
+
+async function openMenuPopup(contextMenu) {
+ info("Opening Share menu popup.");
+ let shareItem = contextMenu.querySelector(".share-tab-url-item");
+ shareItem.openMenu(true);
+ await BrowserTestUtils.waitForPopupEvent(shareItem.menupopup, "shown");
+}
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_share_win.js b/browser/base/content/test/contextMenu/browser_contextmenu_share_win.js
new file mode 100644
index 0000000000..716da584c5
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_share_win.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+const BASE = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+);
+const TEST_URL = BASE + "browser_contextmenu_shareurl.html";
+
+// Setup spies for observing function calls from MacSharingService
+let shareUrlSpy = sinon.spy();
+
+let stub = sinon.stub(gBrowser.ownerGlobal, "WindowsUIUtils").get(() => {
+ return {
+ shareUrl(url, title) {
+ shareUrlSpy(url, title);
+ },
+ };
+});
+
+registerCleanupFunction(async function () {
+ stub.restore();
+});
+
+/**
+ * Test the "Share" item in the tab contextmenu on Windows.
+ */
+add_task(async function test_contextmenu_share_win() {
+ await BrowserTestUtils.withNewTab(TEST_URL, async () => {
+ await openTabContextMenu(gBrowser.selectedTab);
+
+ let contextMenu = document.getElementById("tabContextMenu");
+ let contextMenuClosedPromise = BrowserTestUtils.waitForPopupEvent(
+ contextMenu,
+ "hidden"
+ );
+ let itemCreated = contextMenu.querySelector(".share-tab-url-item");
+ if (!AppConstants.isPlatformAndVersionAtLeast("win", "6.4")) {
+ Assert.ok(!itemCreated, "We only expose share on windows 10 and above");
+ contextMenu.hidePopup();
+ await contextMenuClosedPromise;
+ return;
+ }
+
+ ok(itemCreated, "Got Share item on Windows 10");
+
+ info("Test the correct URL is shared when Share is selected.");
+ EventUtils.synthesizeMouseAtCenter(itemCreated, {});
+ await contextMenuClosedPromise;
+
+ ok(shareUrlSpy.calledOnce, "shareUrl called");
+ let [url, title] = shareUrlSpy.getCall(0).args;
+ is(url, TEST_URL, "Shared correct URL");
+ is(title, "Sharing URL", "Shared correct URL");
+ });
+});
+
+/**
+ * Helper for opening the toolbar context menu.
+ */
+async function openTabContextMenu(tab) {
+ info("Opening tab context menu");
+ let contextMenu = document.getElementById("tabContextMenu");
+ let openTabContextMenuPromise = BrowserTestUtils.waitForPopupEvent(
+ contextMenu,
+ "shown"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(tab, { type: "contextmenu" });
+ await openTabContextMenuPromise;
+}
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_shareurl.html b/browser/base/content/test/contextMenu/browser_contextmenu_shareurl.html
new file mode 100644
index 0000000000..c7fb193972
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_shareurl.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<title>Sharing URL</title>
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_spellcheck.js b/browser/base/content/test/contextMenu/browser_contextmenu_spellcheck.js
new file mode 100644
index 0000000000..6f556a58dd
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_spellcheck.js
@@ -0,0 +1,334 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let contextMenu;
+
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
+const example_base =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/browser/browser/base/content/test/contextMenu/";
+const MAIN_URL = example_base + "subtst_contextmenu_input.html";
+
+add_task(async function test_setup() {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, MAIN_URL);
+
+ const chrome_base =
+ "chrome://mochitests/content/browser/browser/base/content/test/contextMenu/";
+ const contextmenu_common = chrome_base + "contextmenu_common.js";
+ /* import-globals-from contextmenu_common.js */
+ Services.scriptloader.loadSubScript(contextmenu_common, this);
+});
+
+add_task(async function test_text_input_spellcheck() {
+ await test_contextmenu(
+ "#input_spellcheck_no_value",
+ [
+ "context-undo",
+ false,
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ null, // ignore the enabled/disabled states; there are race conditions
+ // in the edit commands but they're not relevant for what we're testing.
+ "context-copy",
+ null,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ null,
+ "context-selectall",
+ null,
+ "---",
+ null,
+ "spell-check-enabled",
+ true,
+ "spell-dictionaries",
+ true,
+ [
+ "spell-check-dictionary-en-US",
+ true,
+ "---",
+ null,
+ "spell-add-dictionaries",
+ true,
+ ],
+ null,
+ ],
+ {
+ waitForSpellCheck: true,
+ async preCheckContextMenuFn() {
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ async function () {
+ let doc = content.document;
+ let input = doc.getElementById("input_spellcheck_no_value");
+ input.setAttribute("spellcheck", "true");
+ input.clientTop; // force layout flush
+ }
+ );
+ },
+ }
+ );
+});
+
+add_task(async function test_text_input_spellcheckwrong() {
+ await test_contextmenu(
+ "#input_spellcheck_incorrect",
+ [
+ "*prodigality",
+ true, // spelling suggestion
+ "spell-add-to-dictionary",
+ true,
+ "---",
+ null,
+ "context-undo",
+ null,
+ "context-redo",
+ null,
+ "---",
+ null,
+ "context-cut",
+ null,
+ "context-copy",
+ null,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ null,
+ "context-selectall",
+ null,
+ "---",
+ null,
+ "spell-check-enabled",
+ true,
+ "spell-dictionaries",
+ true,
+ [
+ "spell-check-dictionary-en-US",
+ true,
+ "---",
+ null,
+ "spell-add-dictionaries",
+ true,
+ ],
+ null,
+ ],
+ { waitForSpellCheck: true }
+ );
+});
+
+const kCorrectItems = [
+ "context-undo",
+ false,
+ "context-redo",
+ false,
+ "---",
+ null,
+ "context-cut",
+ null,
+ "context-copy",
+ null,
+ "context-paste",
+ null, // ignore clipboard state
+ "context-delete",
+ null,
+ "context-selectall",
+ null,
+ "---",
+ null,
+ "spell-check-enabled",
+ true,
+ "spell-dictionaries",
+ true,
+ [
+ "spell-check-dictionary-en-US",
+ true,
+ "---",
+ null,
+ "spell-add-dictionaries",
+ true,
+ ],
+ null,
+];
+
+add_task(async function test_text_input_spellcheckcorrect() {
+ await test_contextmenu("#input_spellcheck_correct", kCorrectItems, {
+ waitForSpellCheck: true,
+ });
+});
+
+add_task(async function test_text_input_spellcheck_deadactor() {
+ await test_contextmenu("#input_spellcheck_correct", kCorrectItems, {
+ waitForSpellCheck: true,
+ keepMenuOpen: true,
+ });
+ let wgp = gBrowser.selectedBrowser.browsingContext.currentWindowGlobal;
+
+ // Now the menu is open, and spellcheck is running, switch to another tab and
+ // close the original:
+ let tab = gBrowser.selectedTab;
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.org");
+ BrowserTestUtils.removeTab(tab);
+ // Ensure we've invalidated the actor
+ await TestUtils.waitForCondition(
+ () => wgp.isClosed,
+ "Waiting for actor to be dead after tab closes"
+ );
+ contextMenu.hidePopup();
+
+ // Now go back to the input testcase:
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, MAIN_URL);
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ MAIN_URL
+ );
+
+ // Check the menu still looks the same, keep it open again:
+ await test_contextmenu("#input_spellcheck_correct", kCorrectItems, {
+ waitForSpellCheck: true,
+ keepMenuOpen: true,
+ });
+
+ // Now navigate the tab, after ensuring there's an unload listener, so
+ // we don't end up in bfcache:
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ content.document.body.setAttribute("onunload", "");
+ });
+ wgp = gBrowser.selectedBrowser.browsingContext.currentWindowGlobal;
+
+ const NEW_URL = MAIN_URL.replace(".com", ".org");
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, NEW_URL);
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ NEW_URL
+ );
+ // Ensure we've invalidated the actor
+ await TestUtils.waitForCondition(
+ () => wgp.isClosed,
+ "Waiting for actor to be dead after onunload"
+ );
+ contextMenu.hidePopup();
+
+ // Check the menu *still* looks the same (and keep it open again):
+ await test_contextmenu("#input_spellcheck_correct", kCorrectItems, {
+ waitForSpellCheck: true,
+ keepMenuOpen: true,
+ });
+
+ // Check what happens if the actor stays alive by loading the same page
+ // again; now the context menu stuff should be destroyed by the menu
+ // hiding, nothing else.
+ wgp = gBrowser.selectedBrowser.browsingContext.currentWindowGlobal;
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, NEW_URL);
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ NEW_URL
+ );
+ contextMenu.hidePopup();
+
+ // Check the menu still looks the same:
+ await test_contextmenu("#input_spellcheck_correct", kCorrectItems, {
+ waitForSpellCheck: true,
+ });
+ // And test it a last time without any navigation:
+ await test_contextmenu("#input_spellcheck_correct", kCorrectItems, {
+ waitForSpellCheck: true,
+ });
+});
+
+add_task(async function test_text_input_spellcheck_multilingual() {
+ if (AppConstants.platform == "macosx") {
+ todo(
+ false,
+ "Need macOS support for closemenu attributes in order to " +
+ "stop the spellcheck menu closing, see bug 1796007."
+ );
+ return;
+ }
+ let sandbox = sinon.createSandbox();
+ registerCleanupFunction(() => sandbox.restore());
+
+ // We need to mock InlineSpellCheckerUI.mRemote's properties, but
+ // InlineSpellCheckerUI.mRemote won't exist until we initialize the context
+ // menu, so do that and then manually reinit the spellcheck bits so
+ // we control them:
+ await test_contextmenu("#input_spellcheck_correct", kCorrectItems, {
+ waitForSpellCheck: true,
+ keepMenuOpen: true,
+ });
+ sandbox
+ .stub(InlineSpellCheckerUI.mRemote, "dictionaryList")
+ .get(() => ["en-US", "nl-NL"]);
+ let setterSpy = sandbox.spy();
+ sandbox
+ .stub(InlineSpellCheckerUI.mRemote, "currentDictionaries")
+ .get(() => ["en-US"])
+ .set(setterSpy);
+ // Re-init the spellcheck items:
+ InlineSpellCheckerUI.clearDictionaryListFromMenu();
+ gContextMenu.initSpellingItems();
+
+ let dictionaryMenu = document.getElementById("spell-dictionaries-menu");
+ let menuOpen = BrowserTestUtils.waitForPopupEvent(dictionaryMenu, "shown");
+ dictionaryMenu.parentNode.openMenu(true);
+ await menuOpen;
+ checkMenu(dictionaryMenu, [
+ "spell-check-dictionary-nl-NL",
+ true,
+ "spell-check-dictionary-en-US",
+ true,
+ "---",
+ null,
+ "spell-add-dictionaries",
+ true,
+ ]);
+ is(
+ dictionaryMenu.children.length,
+ 4,
+ "Should have 2 dictionaries, a separator and 'add more dictionaries' item in the menu."
+ );
+
+ let dictionaryEventPromise = BrowserTestUtils.waitForEvent(
+ document,
+ "spellcheck-changed"
+ );
+ dictionaryMenu.activateItem(
+ dictionaryMenu.querySelector("[data-locale-code*=nl]")
+ );
+ let event = await dictionaryEventPromise;
+ Assert.deepEqual(
+ event.detail?.dictionaries,
+ ["en-US", "nl-NL"],
+ "Should have sent right dictionaries with event."
+ );
+ ok(setterSpy.called, "Should have set currentDictionaries");
+ Assert.deepEqual(
+ setterSpy.firstCall?.args,
+ [["en-US", "nl-NL"]],
+ "Should have called setter with single argument array of 2 dictionaries."
+ );
+ // Allow for the menu to potentially close:
+ await new Promise(r => Services.tm.dispatchToMainThread(r));
+ // Check it hasn't:
+ is(
+ dictionaryMenu.closest("menupopup").state,
+ "open",
+ "Main menu should still be open."
+ );
+ contextMenu.hidePopup();
+});
+
+add_task(async function test_cleanup() {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_touch.js b/browser/base/content/test/contextMenu/browser_contextmenu_touch.js
new file mode 100644
index 0000000000..2f4e5a79c6
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_touch.js
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This test checks that context menus are in touchmode
+ * when opened through a touch event (long tap). */
+
+async function openAndCheckContextMenu(contextMenu, target) {
+ is(contextMenu.state, "closed", "Context menu is initally closed.");
+
+ let popupshown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeNativeTapAtCenter(target, true);
+ await popupshown;
+
+ is(contextMenu.state, "open", "Context menu is open.");
+ is(
+ contextMenu.getAttribute("touchmode"),
+ "true",
+ "Context menu is in touchmode."
+ );
+
+ contextMenu.hidePopup();
+
+ popupshown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(target, { type: "contextmenu" });
+ await popupshown;
+
+ is(contextMenu.state, "open", "Context menu is open.");
+ ok(
+ !contextMenu.hasAttribute("touchmode"),
+ "Context menu is not in touchmode."
+ );
+
+ contextMenu.hidePopup();
+}
+
+// Ensure that we can run touch events properly for windows [10]
+add_setup(async function () {
+ let isWindows = AppConstants.isPlatformAndVersionAtLeast("win", "10.0");
+ await SpecialPowers.pushPrefEnv({
+ set: [["apz.test.fails_with_native_injection", isWindows]],
+ });
+});
+
+// Test the content area context menu.
+add_task(async function test_contentarea_contextmenu_touch() {
+ await BrowserTestUtils.withNewTab("about:blank", async function (browser) {
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ await openAndCheckContextMenu(contextMenu, browser);
+ });
+});
+
+// Test the back and forward buttons.
+add_task(async function test_back_forward_button_contextmenu_touch() {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ await BrowserTestUtils.withNewTab(
+ "http://example.com",
+ async function (browser) {
+ let contextMenu = document.getElementById("backForwardMenu");
+
+ let backbutton = document.getElementById("back-button");
+ let notDisabled = TestUtils.waitForCondition(
+ () => !backbutton.hasAttribute("disabled")
+ );
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ BrowserTestUtils.loadURIString(browser, "http://example.org");
+ await notDisabled;
+ await openAndCheckContextMenu(contextMenu, backbutton);
+
+ let forwardbutton = document.getElementById("forward-button");
+ notDisabled = TestUtils.waitForCondition(
+ () => !forwardbutton.hasAttribute("disabled")
+ );
+ backbutton.click();
+ await notDisabled;
+ await openAndCheckContextMenu(contextMenu, forwardbutton);
+ }
+ );
+});
+
+// Test the toolbar context menu.
+add_task(async function test_toolbar_contextmenu_touch() {
+ let toolbarContextMenu = document.getElementById("toolbar-context-menu");
+ let target = document.getElementById("PanelUI-menu-button");
+ await openAndCheckContextMenu(toolbarContextMenu, target);
+});
+
+// Test the urlbar input context menu.
+add_task(async function test_urlbar_contextmenu_touch() {
+ let urlbar = document.getElementById("urlbar");
+ let textBox = urlbar.querySelector("moz-input-box");
+ let menu = textBox.menupopup;
+ await openAndCheckContextMenu(menu, textBox);
+});
diff --git a/browser/base/content/test/contextMenu/browser_copy_image_link.js b/browser/base/content/test/contextMenu/browser_copy_image_link.js
new file mode 100644
index 0000000000..4853006a61
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_copy_image_link.js
@@ -0,0 +1,40 @@
+/**
+ * Testcase for bug 1719203
+ * <https://bugzilla.mozilla.org/show_bug.cgi?id=1719203>
+ *
+ * Load firebird.png, redirect it to doggy.png, and verify that "Copy Image
+ * Link" copies firebird.png.
+ */
+
+add_task(async function () {
+ // This URL will redirect to doggy.png.
+ const URL_FIREBIRD =
+ "http://mochi.test:8888/browser/browser/base/content/test/contextMenu/firebird.png";
+
+ await BrowserTestUtils.withNewTab(URL_FIREBIRD, async function (browser) {
+ // Click image to show context menu.
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ document,
+ "popupshown"
+ );
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "img",
+ { type: "contextmenu", button: 2 },
+ browser
+ );
+ await popupShownPromise;
+
+ await SimpleTest.promiseClipboardChange(URL_FIREBIRD, () => {
+ document.getElementById("context-copyimage").doCommand();
+ });
+
+ // Close context menu.
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ contextMenu.hidePopup();
+ await popupHiddenPromise;
+ });
+});
diff --git a/browser/base/content/test/contextMenu/browser_strip_on_share_link.js b/browser/base/content/test/contextMenu/browser_strip_on_share_link.js
new file mode 100644
index 0000000000..ba3fd33caa
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_strip_on_share_link.js
@@ -0,0 +1,151 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let listService;
+
+let url =
+ "https://example.com/browser/browser/base/content/test/general/dummy_page.html";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.strip_list", "stripParam"]],
+ });
+
+ // Get the list service so we can wait for it to be fully initialized before running tests.
+ listService = Cc["@mozilla.org/query-stripping-list-service;1"].getService(
+ Ci.nsIURLQueryStrippingListService
+ );
+
+ await listService.testWaitForInit();
+});
+
+/*
+ Tests the strip-on-share feature for in-content links
+ */
+
+// Tests that the link url is properly stripped
+add_task(async function testStrip() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.strip_on_share.enabled", true]],
+ });
+ let strippedURI = "https://www.example.com/?otherParam=1234";
+ await BrowserTestUtils.withNewTab(url, async function (browser) {
+ // Prepare a link
+ await SpecialPowers.spawn(browser, [], async function () {
+ let link = content.document.createElement("a");
+ link.href = "https://www.example.com/?stripParam=1234&otherParam=1234";
+ link.textContent = "link with query param";
+ link.id = "link";
+ content.document.body.appendChild(link);
+ });
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ // Open the context menu
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#link",
+ { type: "contextmenu", button: 2 },
+ browser
+ );
+ await awaitPopupShown;
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ let stripOnShare = contextMenu.querySelector("#context-stripOnShareLink");
+ Assert.ok(
+ BrowserTestUtils.is_visible(stripOnShare),
+ "Menu item is visible"
+ );
+
+ // Make sure the stripped link will be copied to the clipboard
+ await SimpleTest.promiseClipboardChange(strippedURI, () => {
+ contextMenu.activateItem(stripOnShare);
+ });
+ await awaitPopupHidden;
+ });
+});
+
+// Tests that the menu item does not show if the pref is disabled
+add_task(async function testPrefDisabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.strip_on_share.enabled", false]],
+ });
+ await BrowserTestUtils.withNewTab(url, async function (browser) {
+ // Prepare a link
+ await SpecialPowers.spawn(browser, [], async function () {
+ let link = content.document.createElement("a");
+ link.href = "https://www.example.com/?stripParam=1234&otherParam=1234";
+ link.textContent = "link with query param";
+ link.id = "link";
+ content.document.body.appendChild(link);
+ });
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ // Open the context menu
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#link",
+ { type: "contextmenu", button: 2 },
+ browser
+ );
+ await awaitPopupShown;
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ let stripOnShare = contextMenu.querySelector("#context-stripOnShareLink");
+ Assert.ok(
+ !BrowserTestUtils.is_visible(stripOnShare),
+ "Menu item is not visible"
+ );
+ contextMenu.hidePopup();
+ await awaitPopupHidden;
+ });
+});
+
+// Tests that the menu item does not show if there is nothing to strip
+add_task(async function testUnknownQueryParam() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.strip_on_share.enabled", true]],
+ });
+ await BrowserTestUtils.withNewTab(url, async function (browser) {
+ // Prepare a link
+ await SpecialPowers.spawn(browser, [], async function () {
+ let link = content.document.createElement("a");
+ link.href = "https://www.example.com/?otherParam=1234";
+ link.textContent = "link with unknown query param";
+ link.id = "link";
+ content.document.body.appendChild(link);
+ });
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ // open the context menu
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#link",
+ { type: "contextmenu", button: 2 },
+ browser
+ );
+ await awaitPopupShown;
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ let stripOnShare = contextMenu.querySelector("#context-stripOnShareLink");
+ Assert.ok(
+ !BrowserTestUtils.is_visible(stripOnShare),
+ "Menu item is not visible"
+ );
+ contextMenu.hidePopup();
+ await awaitPopupHidden;
+ });
+});
diff --git a/browser/base/content/test/contextMenu/browser_utilityOverlay.js b/browser/base/content/test/contextMenu/browser_utilityOverlay.js
new file mode 100644
index 0000000000..2a3b881c92
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_utilityOverlay.js
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function test_eventMatchesKey() {
+ let eventMatchResult;
+ let key;
+ let checkEvent = function (e) {
+ e.stopPropagation();
+ e.preventDefault();
+ eventMatchResult = eventMatchesKey(e, key);
+ };
+ document.addEventListener("keypress", checkEvent);
+
+ try {
+ key = document.createXULElement("key");
+ let keyset = document.getElementById("mainKeyset");
+ key.setAttribute("key", "t");
+ key.setAttribute("modifiers", "accel");
+ keyset.appendChild(key);
+ EventUtils.synthesizeKey("t", { accelKey: true });
+ is(eventMatchResult, true, "eventMatchesKey: one modifier");
+ keyset.removeChild(key);
+
+ key = document.createXULElement("key");
+ key.setAttribute("key", "g");
+ key.setAttribute("modifiers", "accel,shift");
+ keyset.appendChild(key);
+ EventUtils.synthesizeKey("g", { accelKey: true, shiftKey: true });
+ is(eventMatchResult, true, "eventMatchesKey: combination modifiers");
+ keyset.removeChild(key);
+
+ key = document.createXULElement("key");
+ key.setAttribute("key", "w");
+ key.setAttribute("modifiers", "accel");
+ keyset.appendChild(key);
+ EventUtils.synthesizeKey("f", { accelKey: true });
+ is(eventMatchResult, false, "eventMatchesKey: mismatch keys");
+ keyset.removeChild(key);
+
+ key = document.createXULElement("key");
+ key.setAttribute("keycode", "VK_DELETE");
+ keyset.appendChild(key);
+ EventUtils.synthesizeKey("VK_DELETE", { accelKey: true });
+ is(eventMatchResult, false, "eventMatchesKey: mismatch modifiers");
+ keyset.removeChild(key);
+ } finally {
+ // Make sure to remove the event listener so future tests don't
+ // fail when they simulate key presses.
+ document.removeEventListener("keypress", checkEvent);
+ }
+});
+
+add_task(async function test_getTargetWindow() {
+ is(URILoadingHelper.getTargetWindow(window), window, "got top window");
+});
+
+add_task(async function test_openUILink() {
+ const kURL = "https://example.org/";
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ kURL
+ );
+
+ openUILink(kURL, null, {
+ triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
+ }); // defaults to "current"
+
+ await loadPromise;
+
+ is(tab.linkedBrowser.currentURI.spec, kURL, "example.org loaded");
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/contextMenu/browser_utilityOverlayPrincipal.js b/browser/base/content/test/contextMenu/browser_utilityOverlayPrincipal.js
new file mode 100644
index 0000000000..5b8252b973
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_utilityOverlayPrincipal.js
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const gTests = [test_openUILink_checkPrincipal];
+
+function test() {
+ waitForExplicitFinish();
+ executeSoon(runNextTest);
+}
+
+function runNextTest() {
+ if (gTests.length) {
+ let testFun = gTests.shift();
+ info("Running " + testFun.name);
+ testFun();
+ } else {
+ finish();
+ }
+}
+
+function test_openUILink_checkPrincipal() {
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/"
+ )); // remote tab
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(async function () {
+ is(
+ tab.linkedBrowser.currentURI.spec,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/",
+ "example.com loaded"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ let channel = content.docShell.currentDocumentChannel;
+
+ const loadingPrincipal = channel.loadInfo.loadingPrincipal;
+ is(loadingPrincipal, null, "sanity: correct loadingPrincipal");
+ const triggeringPrincipal = channel.loadInfo.triggeringPrincipal;
+ ok(
+ triggeringPrincipal.isSystemPrincipal,
+ "sanity: correct triggeringPrincipal"
+ );
+ const principalToInherit = channel.loadInfo.principalToInherit;
+ ok(
+ principalToInherit.isNullPrincipal,
+ "sanity: correct principalToInherit"
+ );
+ ok(
+ content.document.nodePrincipal.isContentPrincipal,
+ "sanity: correct doc.nodePrincipal"
+ );
+ is(
+ content.document.nodePrincipal.asciiSpec,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/",
+ "sanity: correct doc.nodePrincipal URL"
+ );
+ });
+
+ gBrowser.removeCurrentTab();
+ runNextTest();
+ });
+
+ // Ensure we get the correct default of "allowInheritPrincipal: false" from openUILink
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ openUILink("http://example.com", null, {
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal({}),
+ }); // defaults to "current"
+}
diff --git a/browser/base/content/test/contextMenu/browser_view_image.js b/browser/base/content/test/contextMenu/browser_view_image.js
new file mode 100644
index 0000000000..485fdf3fb2
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_view_image.js
@@ -0,0 +1,197 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const chrome_base = getRootDirectory(gTestPath);
+
+/* import-globals-from contextmenu_common.js */
+Services.scriptloader.loadSubScript(
+ chrome_base + "contextmenu_common.js",
+ this
+);
+const http_base = chrome_base.replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+async function test_view_image_works({ page, selector, urlChecker }) {
+ let mainURL = http_base + page;
+ let accel = AppConstants.platform == "macosx" ? "metaKey" : "ctrlKey";
+ let tests = {
+ tab: {
+ modifiers: { [accel]: true },
+ async loadedPromise() {
+ return BrowserTestUtils.waitForNewTab(gBrowser, urlChecker, true).then(
+ t => t.linkedBrowser
+ );
+ },
+ cleanup(browser) {
+ BrowserTestUtils.removeTab(gBrowser.getTabForBrowser(browser));
+ },
+ },
+ window: {
+ modifiers: { shiftKey: true },
+ async loadedPromise() {
+ // Unfortunately we can't predict the URL so can't just pass that to waitForNewWindow
+ let w = await BrowserTestUtils.waitForNewWindow();
+ let browser = w.gBrowser.selectedBrowser;
+ let getCx = () => browser.browsingContext;
+ await TestUtils.waitForCondition(
+ () =>
+ getCx() && urlChecker(getCx().currentWindowGlobal.documentURI.spec)
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ return ContentTaskUtils.waitForCondition(
+ () => content.document.readyState == "complete"
+ );
+ });
+ return browser;
+ },
+ async cleanup(browser) {
+ return BrowserTestUtils.closeWindow(browser.ownerGlobal);
+ },
+ },
+ tab_default: {
+ modifiers: {},
+ async loadedPromise() {
+ return BrowserTestUtils.waitForNewTab(gBrowser, urlChecker, true).then(
+ t => {
+ is(t.selected, false, "Tab should not be selected.");
+ return t.linkedBrowser;
+ }
+ );
+ },
+ cleanup(browser) {
+ is(gBrowser.tabs.length, 3, "number of tabs");
+ BrowserTestUtils.removeTab(gBrowser.getTabForBrowser(browser));
+ },
+ },
+ tab_default_flip_bg_pref: {
+ prefs: [["browser.tabs.loadInBackground", false]],
+ modifiers: {},
+ async loadedPromise() {
+ return BrowserTestUtils.waitForNewTab(gBrowser, urlChecker, true).then(
+ t => {
+ is(t.selected, true, "Tab should be selected with pref flipped.");
+ return t.linkedBrowser;
+ }
+ );
+ },
+ cleanup(browser) {
+ is(gBrowser.tabs.length, 3, "number of tabs");
+ BrowserTestUtils.removeTab(gBrowser.getTabForBrowser(browser));
+ },
+ },
+ };
+ await BrowserTestUtils.withNewTab(mainURL, async browser => {
+ await SpecialPowers.spawn(browser, [], () => {
+ return ContentTaskUtils.waitForCondition(
+ () => !content.document.documentElement.classList.contains("wait")
+ );
+ });
+ for (let [testLabel, test] of Object.entries(tests)) {
+ if (test.prefs) {
+ await SpecialPowers.pushPrefEnv({ set: test.prefs });
+ }
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ is(
+ contextMenu.state,
+ "closed",
+ `${testLabel} - checking if popup is closed`
+ );
+ let promisePopupShown = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ await BrowserTestUtils.synthesizeMouse(
+ selector,
+ 2,
+ 2,
+ { type: "contextmenu", button: 2 },
+ browser
+ );
+ await promisePopupShown;
+ info(`${testLabel} - Popup Shown`);
+ let promisePopupHidden = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ let browserPromise = test.loadedPromise();
+ contextMenu.activateItem(
+ document.getElementById("context-viewimage"),
+ test.modifiers
+ );
+ await promisePopupHidden;
+
+ let newBrowser = await browserPromise;
+ let { documentURI } = newBrowser.browsingContext.currentWindowGlobal;
+ if (documentURI.spec.startsWith("data:image/svg")) {
+ await SpecialPowers.spawn(newBrowser, [testLabel], msgPrefix => {
+ let svgEl = content.document.querySelector("svg");
+ ok(svgEl, `${msgPrefix} - should have loaded SVG.`);
+ is(svgEl.height.baseVal.value, 500, `${msgPrefix} - SVG has height`);
+ is(svgEl.width.baseVal.value, 500, `${msgPrefix} - SVG has height`);
+ });
+ } else {
+ await SpecialPowers.spawn(newBrowser, [testLabel], msgPrefix => {
+ let img = content.document.querySelector("img");
+ ok(
+ img instanceof Ci.nsIImageLoadingContent,
+ `${msgPrefix} - Image should have loaded content.`
+ );
+ const request = img.getRequest(
+ Ci.nsIImageLoadingContent.CURRENT_REQUEST
+ );
+ ok(
+ request.imageStatus & request.STATUS_LOAD_COMPLETE,
+ `${msgPrefix} - Should have loaded image.`
+ );
+ });
+ }
+ await test.cleanup(newBrowser);
+ if (test.prefs) {
+ await SpecialPowers.popPrefEnv();
+ }
+ }
+ });
+}
+
+/**
+ * Verify that the 'view image' context menu in a new tab for a canvas works,
+ * when opened in a new tab, a new window, or in the same tab.
+ */
+add_task(async function test_view_image_canvas_works() {
+ await test_view_image_works({
+ page: "subtst_contextmenu.html",
+ selector: "#test-canvas",
+ urlChecker: url => url.startsWith("blob:"),
+ });
+});
+
+/**
+ * Test for https://bugzilla.mozilla.org/show_bug.cgi?id=1625786
+ */
+add_task(async function test_view_image_revoked_cached_blob() {
+ await test_view_image_works({
+ page: "test_view_image_revoked_cached_blob.html",
+ selector: "#second",
+ urlChecker: url => url.startsWith("blob:"),
+ });
+});
+
+/**
+ * Test for https://bugzilla.mozilla.org/show_bug.cgi?id=1738190
+ * Inline SVG data URIs as a background image should also open.
+ */
+add_task(async function test_view_image_inline_svg_bgimage() {
+ await SpecialPowers.pushPrefEnv({
+ // This is the default but we turn it off for unit tests.
+ set: [["security.data_uri.block_toplevel_data_uri_navigations", true]],
+ });
+ await test_view_image_works({
+ page: "test_view_image_inline_svg.html",
+ selector: "body",
+ urlChecker: url => url.startsWith("data:"),
+ });
+});
diff --git a/browser/base/content/test/contextMenu/bug1798178.sjs b/browser/base/content/test/contextMenu/bug1798178.sjs
new file mode 100644
index 0000000000..790dc2bee5
--- /dev/null
+++ b/browser/base/content/test/contextMenu/bug1798178.sjs
@@ -0,0 +1,9 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("X-Content-Type-Options", "nosniff");
+ response.write("Hello");
+}
diff --git a/browser/base/content/test/contextMenu/contextmenu_common.js b/browser/base/content/test/contextMenu/contextmenu_common.js
new file mode 100644
index 0000000000..ac61aa2a3a
--- /dev/null
+++ b/browser/base/content/test/contextMenu/contextmenu_common.js
@@ -0,0 +1,437 @@
+// This file expects contextMenu to be defined in the scope it is loaded into.
+/* global contextMenu:true */
+
+var lastElement;
+const FRAME_OS_PID = "context-frameOsPid";
+
+function openContextMenuFor(element, shiftkey, waitForSpellCheck) {
+ // Context menu should be closed before we open it again.
+ is(
+ SpecialPowers.wrap(contextMenu).state,
+ "closed",
+ "checking if popup is closed"
+ );
+
+ if (lastElement) {
+ lastElement.blur();
+ }
+ element.focus();
+
+ // Some elements need time to focus and spellcheck before any tests are
+ // run on them.
+ function actuallyOpenContextMenuFor() {
+ lastElement = element;
+ var eventDetails = { type: "contextmenu", button: 2, shiftKey: shiftkey };
+ synthesizeMouse(element, 2, 2, eventDetails, element.ownerGlobal);
+ }
+
+ if (waitForSpellCheck) {
+ var { onSpellCheck } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://testing-common/AsyncSpellCheckTestHelper.sys.mjs"
+ );
+ onSpellCheck(element, actuallyOpenContextMenuFor);
+ } else {
+ actuallyOpenContextMenuFor();
+ }
+}
+
+function closeContextMenu() {
+ contextMenu.hidePopup();
+}
+
+function getVisibleMenuItems(aMenu, aData) {
+ var items = [];
+ var accessKeys = {};
+ for (var i = 0; i < aMenu.children.length; i++) {
+ var item = aMenu.children[i];
+ if (item.hidden) {
+ continue;
+ }
+
+ var key = item.accessKey;
+ if (key) {
+ key = key.toLowerCase();
+ }
+
+ if (item.nodeName == "menuitem") {
+ var isGenerated =
+ item.classList.contains("spell-suggestion") ||
+ item.classList.contains("sendtab-target");
+ if (isGenerated) {
+ is(item.id, "", "child menuitem #" + i + " is generated");
+ } else {
+ ok(item.id, "child menuitem #" + i + " has an ID");
+ }
+ var label = item.getAttribute("label");
+ ok(label.length, "menuitem " + item.id + " has a label");
+ if (isGenerated) {
+ is(key, "", "Generated items shouldn't have an access key");
+ items.push("*" + label);
+ } else if (
+ item.id.indexOf("spell-check-dictionary-") != 0 &&
+ item.id != "spell-no-suggestions" &&
+ item.id != "spell-add-dictionaries-main" &&
+ item.id != "context-savelinktopocket" &&
+ item.id != "fill-login-no-logins" &&
+ // Inspect accessibility properties does not have an access key. See
+ // bug 1630717 for more details.
+ item.id != "context-inspect-a11y" &&
+ !item.id.includes("context-media-playbackrate")
+ ) {
+ if (item.id != FRAME_OS_PID) {
+ ok(key, "menuitem " + item.id + " has an access key");
+ }
+ if (accessKeys[key]) {
+ ok(
+ false,
+ "menuitem " + item.id + " has same accesskey as " + accessKeys[key]
+ );
+ } else {
+ accessKeys[key] = item.id;
+ }
+ }
+ if (!isGenerated) {
+ items.push(item.id);
+ }
+ items.push(!item.disabled);
+ } else if (item.nodeName == "menuseparator") {
+ ok(true, "--- seperator id is " + item.id);
+ items.push("---");
+ items.push(null);
+ } else if (item.nodeName == "menu") {
+ ok(item.id, "child menu #" + i + " has an ID");
+ ok(key, "menu has an access key");
+ if (accessKeys[key]) {
+ ok(
+ false,
+ "menu " + item.id + " has same accesskey as " + accessKeys[key]
+ );
+ } else {
+ accessKeys[key] = item.id;
+ }
+ items.push(item.id);
+ items.push(!item.disabled);
+ // Add a dummy item so that the indexes in checkMenu are the same
+ // for expectedItems and actualItems.
+ items.push([]);
+ items.push(null);
+ } else if (item.nodeName == "menugroup") {
+ ok(item.id, "child menugroup #" + i + " has an ID");
+ items.push(item.id);
+ items.push(!item.disabled);
+ var menugroupChildren = [];
+ for (var child of item.children) {
+ if (child.hidden) {
+ continue;
+ }
+
+ menugroupChildren.push([child.id, !child.disabled]);
+ }
+ items.push(menugroupChildren);
+ items.push(null);
+ } else {
+ ok(
+ false,
+ "child #" +
+ i +
+ " of menu ID " +
+ aMenu.id +
+ " has an unknown type (" +
+ item.nodeName +
+ ")"
+ );
+ }
+ }
+ return items;
+}
+
+function checkContextMenu(expectedItems) {
+ is(contextMenu.state, "open", "checking if popup is open");
+ var data = { generatedSubmenuId: 1 };
+ checkMenu(contextMenu, expectedItems, data);
+}
+
+function checkMenuItem(
+ actualItem,
+ actualEnabled,
+ expectedItem,
+ expectedEnabled,
+ index
+) {
+ is(
+ `${actualItem}`,
+ expectedItem,
+ "checking item #" + index / 2 + " (" + expectedItem + ") name"
+ );
+
+ if (
+ (typeof expectedEnabled == "object" && expectedEnabled != null) ||
+ (typeof actualEnabled == "object" && actualEnabled != null)
+ ) {
+ ok(!(actualEnabled == null), "actualEnabled is not null");
+ ok(!(expectedEnabled == null), "expectedEnabled is not null");
+ is(typeof actualEnabled, typeof expectedEnabled, "checking types");
+
+ if (
+ typeof actualEnabled != typeof expectedEnabled ||
+ actualEnabled == null ||
+ expectedEnabled == null
+ ) {
+ return;
+ }
+
+ is(
+ actualEnabled.type,
+ expectedEnabled.type,
+ "checking item #" + index / 2 + " (" + expectedItem + ") type attr value"
+ );
+ var icon = actualEnabled.icon;
+ if (icon) {
+ var tmp = "";
+ var j = icon.length - 1;
+ while (j && icon[j] != "/") {
+ tmp = icon[j--] + tmp;
+ }
+ icon = tmp;
+ }
+ is(
+ icon,
+ expectedEnabled.icon,
+ "checking item #" + index / 2 + " (" + expectedItem + ") icon attr value"
+ );
+ is(
+ actualEnabled.checked,
+ expectedEnabled.checked,
+ "checking item #" + index / 2 + " (" + expectedItem + ") has checked attr"
+ );
+ is(
+ actualEnabled.disabled,
+ expectedEnabled.disabled,
+ "checking item #" +
+ index / 2 +
+ " (" +
+ expectedItem +
+ ") has disabled attr"
+ );
+ } else if (expectedEnabled != null) {
+ is(
+ actualEnabled,
+ expectedEnabled,
+ "checking item #" + index / 2 + " (" + expectedItem + ") enabled state"
+ );
+ }
+}
+
+/*
+ * checkMenu - checks to see if the specified <menupopup> contains the
+ * expected items and state.
+ * expectedItems is a array of (1) item IDs and (2) a boolean specifying if
+ * the item is enabled or not (or null to ignore it). Submenus can be checked
+ * by providing a nested array entry after the expected <menu> ID.
+ * For example: ["blah", true, // item enabled
+ * "submenu", null, // submenu
+ * ["sub1", true, // submenu contents
+ * "sub2", false], null, // submenu contents
+ * "lol", false] // item disabled
+ *
+ */
+function checkMenu(menu, expectedItems, data) {
+ var actualItems = getVisibleMenuItems(menu, data);
+ // ok(false, "Items are: " + actualItems);
+ for (var i = 0; i < expectedItems.length; i += 2) {
+ var actualItem = actualItems[i];
+ var actualEnabled = actualItems[i + 1];
+ var expectedItem = expectedItems[i];
+ var expectedEnabled = expectedItems[i + 1];
+ if (expectedItem instanceof Array) {
+ ok(true, "Checking submenu/menugroup...");
+ var previousId = expectedItems[i - 2]; // The last item was the menu ID.
+ var previousItem = menu.getElementsByAttribute("id", previousId)[0];
+ ok(
+ previousItem,
+ (previousItem ? previousItem.nodeName : "item") +
+ " with previous id (" +
+ previousId +
+ ") found"
+ );
+ if (previousItem && previousItem.nodeName == "menu") {
+ ok(previousItem, "got a submenu element of id='" + previousId + "'");
+ is(
+ previousItem.nodeName,
+ "menu",
+ "submenu element of id='" + previousId + "' has expected nodeName"
+ );
+ checkMenu(previousItem.menupopup, expectedItem, data, i);
+ } else if (previousItem && previousItem.nodeName == "menugroup") {
+ ok(expectedItem.length, "menugroup must not be empty");
+ for (var j = 0; j < expectedItem.length / 2; j++) {
+ checkMenuItem(
+ actualItems[i][j][0],
+ actualItems[i][j][1],
+ expectedItem[j * 2],
+ expectedItem[j * 2 + 1],
+ i + j * 2
+ );
+ }
+ i += j;
+ } else {
+ ok(false, "previous item is not a menu or menugroup");
+ }
+ } else {
+ checkMenuItem(
+ actualItem,
+ actualEnabled,
+ expectedItem,
+ expectedEnabled,
+ i
+ );
+ }
+ }
+ // Could find unexpected extra items at the end...
+ is(
+ actualItems.length,
+ expectedItems.length,
+ "checking expected number of menu entries"
+ );
+}
+
+let lastElementSelector = null;
+/**
+ * Right-clicks on the element that matches `selector` and checks the
+ * context menu that appears against the `menuItems` array.
+ *
+ * @param {String} selector
+ * A selector passed to querySelector to find
+ * the element that will be referenced.
+ * @param {Array} menuItems
+ * An array of menuitem ids and their associated enabled state. A state
+ * of null means that it will be ignored. Ids of '---' are used for
+ * menuseparators.
+ * @param {Object} options, optional
+ * skipFocusChange: don't move focus to the element before test, useful
+ * if you want to delay spell-check initialization
+ * offsetX: horizontal mouse offset from the top-left corner of
+ * the element, optional
+ * offsetY: vertical mouse offset from the top-left corner of the
+ * element, optional
+ * centered: if true, mouse position is centered in element, defaults
+ * to true if offsetX and offsetY are not provided
+ * waitForSpellCheck: wait until spellcheck is initialized before
+ * starting test
+ * preCheckContextMenuFn: callback to run before opening menu
+ * onContextMenuShown: callback to run when the context menu is shown
+ * postCheckContextMenuFn: callback to run after opening menu
+ * keepMenuOpen: if true, we do not call hidePopup, the consumer is
+ * responsible for calling it.
+ * @return {Promise} resolved after the test finishes
+ */
+async function test_contextmenu(selector, menuItems, options = {}) {
+ contextMenu = document.getElementById("contentAreaContextMenu");
+ is(contextMenu.state, "closed", "checking if popup is closed");
+
+ // Default to centered if no positioning is defined.
+ if (!options.offsetX && !options.offsetY) {
+ options.centered = true;
+ }
+
+ if (!options.skipFocusChange) {
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [[lastElementSelector, selector]],
+ async function ([contentLastElementSelector, contentSelector]) {
+ if (contentLastElementSelector) {
+ let contentLastElement = content.document.querySelector(
+ contentLastElementSelector
+ );
+ contentLastElement.blur();
+ }
+ let element = content.document.querySelector(contentSelector);
+ element.focus();
+ }
+ );
+ lastElementSelector = selector;
+ info(`Moved focus to ${selector}`);
+ }
+
+ if (options.preCheckContextMenuFn) {
+ await options.preCheckContextMenuFn();
+ info("Completed preCheckContextMenuFn");
+ }
+
+ if (options.waitForSpellCheck) {
+ info("Waiting for spell check");
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [selector],
+ async function (contentSelector) {
+ let { onSpellCheck } = ChromeUtils.importESModule(
+ "resource://testing-common/AsyncSpellCheckTestHelper.sys.mjs"
+ );
+ let element = content.document.querySelector(contentSelector);
+ await new Promise(resolve => onSpellCheck(element, resolve));
+ info("Spell check running");
+ }
+ );
+ }
+
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ await BrowserTestUtils.synthesizeMouse(
+ selector,
+ options.offsetX || 0,
+ options.offsetY || 0,
+ {
+ type: "contextmenu",
+ button: 2,
+ shiftkey: options.shiftkey,
+ centered: options.centered,
+ },
+ gBrowser.selectedBrowser
+ );
+ await awaitPopupShown;
+ info("Popup Shown");
+
+ if (options.onContextMenuShown) {
+ await options.onContextMenuShown();
+ info("Completed onContextMenuShown");
+ }
+
+ if (menuItems) {
+ if (Services.prefs.getBoolPref("devtools.inspector.enabled", true)) {
+ const inspectItems =
+ menuItems.includes("context-viewsource") ||
+ menuItems.includes("context-viewpartialsource-selection")
+ ? []
+ : ["---", null];
+ if (
+ Services.prefs.getBoolPref("devtools.accessibility.enabled", true) &&
+ (Services.prefs.getBoolPref("devtools.everOpened", false) ||
+ Services.prefs.getIntPref("devtools.selfxss.count", 0) > 0)
+ ) {
+ inspectItems.push("context-inspect-a11y", true);
+ }
+ inspectItems.push("context-inspect", true);
+
+ menuItems = menuItems.concat(inspectItems);
+ }
+
+ checkContextMenu(menuItems);
+ }
+
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+
+ if (options.postCheckContextMenuFn) {
+ await options.postCheckContextMenuFn();
+ info("Completed postCheckContextMenuFn");
+ }
+
+ if (!options.keepMenuOpen) {
+ contextMenu.hidePopup();
+ await awaitPopupHidden;
+ }
+}
diff --git a/browser/base/content/test/contextMenu/ctxmenu-image.png b/browser/base/content/test/contextMenu/ctxmenu-image.png
new file mode 100644
index 0000000000..4c3be50847
--- /dev/null
+++ b/browser/base/content/test/contextMenu/ctxmenu-image.png
Binary files differ
diff --git a/browser/base/content/test/contextMenu/doggy.png b/browser/base/content/test/contextMenu/doggy.png
new file mode 100644
index 0000000000..73632d3229
--- /dev/null
+++ b/browser/base/content/test/contextMenu/doggy.png
Binary files differ
diff --git a/browser/base/content/test/contextMenu/file_bug1798178.html b/browser/base/content/test/contextMenu/file_bug1798178.html
new file mode 100644
index 0000000000..49e0092a4f
--- /dev/null
+++ b/browser/base/content/test/contextMenu/file_bug1798178.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <a href="https://example.org/browser/browser/base/content/test/contextMenu/bug1798178.sjs">Download Link</a>
+ </body>
+</html>
diff --git a/browser/base/content/test/contextMenu/firebird.png b/browser/base/content/test/contextMenu/firebird.png
new file mode 100644
index 0000000000..de5c22f8ce
--- /dev/null
+++ b/browser/base/content/test/contextMenu/firebird.png
Binary files differ
diff --git a/browser/base/content/test/contextMenu/firebird.png^headers^ b/browser/base/content/test/contextMenu/firebird.png^headers^
new file mode 100644
index 0000000000..2918fdbe5f
--- /dev/null
+++ b/browser/base/content/test/contextMenu/firebird.png^headers^
@@ -0,0 +1,2 @@
+HTTP 302 Found
+Location: doggy.png
diff --git a/browser/base/content/test/contextMenu/subtst_contextmenu.html b/browser/base/content/test/contextMenu/subtst_contextmenu.html
new file mode 100644
index 0000000000..2c263fbce4
--- /dev/null
+++ b/browser/base/content/test/contextMenu/subtst_contextmenu.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Subtest for browser context menu</title>
+</head>
+<body>
+Browser context menu subtest.
+
+<div id="test-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
+<a id="test-link" href="http://mozilla.com">Click the monkey!</a>
+<div id="shadow-host"></div>
+<a href="http://mozilla.com" style="display: block">
+ <span id="shadow-host-in-link"></span>
+</a>
+<script>
+document.getElementById("shadow-host").attachShadow({ mode: "closed" }).innerHTML =
+ "<a href='http://mozilla.com'>Click the monkey!</a>";
+document.getElementById("shadow-host-in-link").attachShadow({ mode: "closed" }).innerHTML =
+ "<span>Click the monkey!</span>";
+</script>
+<a id="test-mailto" href="mailto:codemonkey@mozilla.com">Mail the monkey!</a><br>
+<a id="test-tel" href="tel:555-123-4567">Call random number!</a><br>
+<input id="test-input"><br>
+<img id="test-image" src="ctxmenu-image.png">
+<svg>
+ <image id="test-svg-image" href="ctxmenu-image.png"/>
+</svg>
+<canvas id="test-canvas" width="100" height="100" style="background-color: blue"></canvas>
+<video controls id="test-video-ok" src="video.ogg" width="100" height="100" style="background-color: green"></video>
+<video id="test-audio-in-video" src="audio.ogg" width="100" height="100" style="background-color: red"></video>
+<video controls id="test-video-bad" src="bogus.duh" width="100" height="100" style="background-color: orange"></video>
+<video controls id="test-video-bad2" width="100" height="100" style="background-color: yellow">
+ <source src="bogus.duh" type="video/durrrr;">
+</video>
+<iframe id="test-iframe" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-video-in-iframe" src="video.ogg" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-audio-in-iframe" src="audio.ogg" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-image-in-iframe" src="ctxmenu-image.png" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-pdf-viewer-in-frame" src="file_pdfjs_test.pdf" width="100" height="100" style="border: 1px solid black"></iframe>
+<textarea id="test-textarea">chssseesbbbie</textarea> <!-- a weird word which generates only one suggestion -->
+<div id="test-contenteditable" contenteditable="true">chssseefsbbbie</div> <!-- a more weird word which generates no suggestions -->
+<div id="test-contenteditable-spellcheck-false" contenteditable="true" spellcheck="false">test</div> <!-- No Check Spelling menu item -->
+<div id="test-dom-full-screen">DOM full screen FTW</div>
+<div id="test-select-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
+<div id="test-select-text-link">http://mozilla.com</div>
+<a id="test-image-link" href="#"><img src="ctxmenu-image.png"></a>
+<input id="test-select-input-text" type="text" value="input">
+<input id="test-select-input-text-type-password" type="password" value="password">
+<img id="test-longdesc" src="ctxmenu-image.png" longdesc="http://www.mozilla.org"></embed>
+<iframe id="test-srcdoc" width="98" height="98" srcdoc="Hello World" style="border: 1px solid black"></iframe>
+<svg id="svg-with-link" width=10 height=10><a xlink:href="http://example.com/"><circle cx="50%" cy="50%" r="50%" fill="blue"/></a></svg>
+<svg id="svg-with-link2" width=10 height=10><a xlink:href="http://example.com/" xlink:type="simple"><circle cx="50%" cy="50%" r="50%" fill="green"/></a></svg>
+<svg id="svg-with-link3" width=10 height=10><a href="http://example.com/"><circle cx="50%" cy="50%" r="50%" fill="red"/></a></svg>
+<svg id="svg-with-relative-link" width=10 height=10><a xlink:href="/"><circle cx="50%" cy="50%" r="50%" fill="blue"/></a></svg>
+<svg id="svg-with-relative-link2" width=10 height=10><a xlink:href="/" xlink:type="simple"><circle cx="50%" cy="50%" r="50%" fill="green"/></a></svg>
+<svg id="svg-with-relative-link3" width=10 height=10><a href="/"><circle cx="50%" cy="50%" r="50%" fill="red"/></a></svg>
+<span id="test-background-image" style="background-image: url('ctxmenu-image.png')">Text with background
+ <a id='test-background-image-link' href="about:blank">image</a>
+ .
+</span></body>
+</html>
diff --git a/browser/base/content/test/contextMenu/subtst_contextmenu_input.html b/browser/base/content/test/contextMenu/subtst_contextmenu_input.html
new file mode 100644
index 0000000000..a34cbbe122
--- /dev/null
+++ b/browser/base/content/test/contextMenu/subtst_contextmenu_input.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Subtest for browser context menu</title>
+</head>
+<body>
+ Browser context menu subtest.
+ <input id="input_text">
+ <input id="input_spellcheck_no_value">
+ <input id="input_spellcheck_incorrect" spellcheck="true" value="prodkjfgigrty">
+ <input id="input_spellcheck_correct" spellcheck="true" value="foo">
+ <input id="input_disabled" disabled="true">
+ <input id="input_password">
+ <input id="input_email" type="email">
+ <input id="input_tel" type="tel">
+ <input id="input_url" type="url">
+ <input id="input_number" type="number">
+ <input id="input_date" type="date">
+ <input id="input_time" type="time">
+ <input id="input_color" type="color">
+ <input id="input_range" type="range">
+ <input id="input_search" type="search">
+ <input id="input_datetime" type="datetime">
+ <input id="input_month" type="month">
+ <input id="input_week" type="week">
+ <input id="input_datetime-local" type="datetime-local">
+ <input id="input_readonly" readonly="true">
+ <input id="input_username" name="username">
+</body>
+</html>
diff --git a/browser/base/content/test/contextMenu/subtst_contextmenu_keyword.html b/browser/base/content/test/contextMenu/subtst_contextmenu_keyword.html
new file mode 100644
index 0000000000..a0f2b0584f
--- /dev/null
+++ b/browser/base/content/test/contextMenu/subtst_contextmenu_keyword.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Subtest for browser context menu</title>
+</head>
+<body>
+ Browser context menu subtest.
+ <input id="input_text_no_form">
+ <form id="form_with_password">
+ <input id="login_text">
+ <input id="input_password" type="password">
+ </form>
+ <form id="form_without_password">
+ <input id="search_text">
+ </form>
+</body>
+</html>
diff --git a/browser/base/content/test/contextMenu/subtst_contextmenu_webext.html b/browser/base/content/test/contextMenu/subtst_contextmenu_webext.html
new file mode 100644
index 0000000000..ac3b5415dd
--- /dev/null
+++ b/browser/base/content/test/contextMenu/subtst_contextmenu_webext.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Subtest for browser context menu</title>
+</head>
+<body>
+ Browser context menu subtest.
+ <a href="moz-extension://foo-bar/tab.html" id="link">Link to an extension resource</a>
+ <video src="moz-extension://foo-bar/video.ogg" id="video"></video>
+</body>
+</html>
diff --git a/browser/base/content/test/contextMenu/subtst_contextmenu_xul.xhtml b/browser/base/content/test/contextMenu/subtst_contextmenu_xul.xhtml
new file mode 100644
index 0000000000..c8ff92a76c
--- /dev/null
+++ b/browser/base/content/test/contextMenu/subtst_contextmenu_xul.xhtml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+ <label id="test-xul-text-link-label" is="text-link" value="XUL text-link label" href="https://www.mozilla.com"/>
+</window>
diff --git a/browser/base/content/test/contextMenu/test_contextmenu_iframe.html b/browser/base/content/test/contextMenu/test_contextmenu_iframe.html
new file mode 100644
index 0000000000..cf5b871ecd
--- /dev/null
+++ b/browser/base/content/test/contextMenu/test_contextmenu_iframe.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Subtest for browser context menu iframes</title>
+</head>
+<body>
+Browser context menu iframe subtest.
+
+<iframe src="https://example.com/" id="iframe"></iframe>
+</body>
+</html>
diff --git a/browser/base/content/test/contextMenu/test_contextmenu_links.html b/browser/base/content/test/contextMenu/test_contextmenu_links.html
new file mode 100644
index 0000000000..650c136f99
--- /dev/null
+++ b/browser/base/content/test/contextMenu/test_contextmenu_links.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Subtest for browser context menu links</title>
+</head>
+<body>
+Browser context menu link subtest.
+
+<a id="test-link" href="https://example.com">Click the monkey!</a>
+<a id="test-image-link" href="/"><img src="ctxmenu-image.png"></a>
+<svg id="svg-with-link" width=10 height=10><a xlink:href="https://example.com/"><circle cx="50%" cy="50%" r="50%" fill="blue"/></a></svg>
+<svg id="svg-with-relative-link" width=10 height=10><a xlink:href="/"><circle cx="50%" cy="50%" r="50%" fill="blue"/></a></svg>
+</body>
+</html>
diff --git a/browser/base/content/test/contextMenu/test_view_image_inline_svg.html b/browser/base/content/test/contextMenu/test_view_image_inline_svg.html
new file mode 100644
index 0000000000..42a41e42cb
--- /dev/null
+++ b/browser/base/content/test/contextMenu/test_view_image_inline_svg.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html><head>
+<style>
+body {
+ background: fixed #222 url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjUwMCIgd2lkdGg9IjUwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGZpbHRlciBpZD0iYSI+PGZlVHVyYnVsZW5jZSBiYXNlRnJlcXVlbmN5PSIuOSIgbnVtT2N0YXZlcz0iMTAiIHN0aXRjaFRpbGVzPSJzdGl0Y2giIHR5cGU9ImZyYWN0YWxOb2lzZSIvPjwvZmlsdGVyPjxwYXRoIGQ9Im0wIDBoNTAwdjUwMGgtNTAweiIgZmlsbD0iIzExMSIvPjxwYXRoIGQ9Im0wIDBoNTAwdjUwMGgtNTAweiIgZmlsdGVyPSJ1cmwoI2EpIiBvcGFjaXR5PSIuMiIvPjwvc3ZnPgo=");
+ background-size: cover;
+ color: #ccc;
+}
+</style>
+</head>
+<body>
+This page has an inline SVG image as a background.
+
+
+</body></html>
diff --git a/browser/base/content/test/contextMenu/test_view_image_revoked_cached_blob.html b/browser/base/content/test/contextMenu/test_view_image_revoked_cached_blob.html
new file mode 100644
index 0000000000..ba130c793a
--- /dev/null
+++ b/browser/base/content/test/contextMenu/test_view_image_revoked_cached_blob.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html class="wait">
+<meta charset="utf-8">
+<title>currentSrc is right even if underlying image is a shared blob</title>
+<img id="first">
+<img id="second">
+<script>
+(async function() {
+ let canvas = document.createElement("canvas");
+ canvas.width = 100;
+ canvas.height = 100;
+ let ctx = canvas.getContext("2d");
+ ctx.fillStyle = "green";
+ ctx.rect(0, 0, 100, 100);
+ ctx.fill();
+
+ let blob = await new Promise(resolve => canvas.toBlob(resolve));
+
+ let first = document.querySelector("#first");
+ let second = document.querySelector("#second");
+
+ let firstLoad = new Promise(resolve => {
+ first.addEventListener("load", resolve, { once: true });
+ });
+
+ let secondLoad = new Promise(resolve => {
+ second.addEventListener("load", resolve, { once: true });
+ });
+
+ let uri1 = URL.createObjectURL(blob);
+ let uri2 = URL.createObjectURL(blob);
+ first.src = uri1;
+ second.src = uri2;
+
+ await firstLoad;
+ await secondLoad;
+ URL.revokeObjectURL(uri1);
+ document.documentElement.className = "";
+}());
+</script>