diff options
Diffstat (limited to 'browser/base/content/test/contextMenu')
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 Binary files differnew file mode 100644 index 0000000000..4c3be50847 --- /dev/null +++ b/browser/base/content/test/contextMenu/ctxmenu-image.png diff --git a/browser/base/content/test/contextMenu/doggy.png b/browser/base/content/test/contextMenu/doggy.png Binary files differnew file mode 100644 index 0000000000..73632d3229 --- /dev/null +++ b/browser/base/content/test/contextMenu/doggy.png 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 Binary files differnew file mode 100644 index 0000000000..de5c22f8ce --- /dev/null +++ b/browser/base/content/test/contextMenu/firebird.png 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> |