diff options
Diffstat (limited to 'browser/base/content/test/contextMenu')
24 files changed, 4383 insertions, 0 deletions
diff --git a/browser/base/content/test/contextMenu/.eslintrc.js b/browser/base/content/test/contextMenu/.eslintrc.js new file mode 100644 index 0000000000..1779fd7f1c --- /dev/null +++ b/browser/base/content/test/contextMenu/.eslintrc.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = { + extends: ["plugin:mozilla/browser-test"], +}; diff --git a/browser/base/content/test/contextMenu/browser.ini b/browser/base/content/test/contextMenu/browser.ini new file mode 100644 index 0000000000..a8fea52423 --- /dev/null +++ b/browser/base/content/test/contextMenu/browser.ini @@ -0,0 +1,38 @@ +[DEFAULT] +prefs = + plugin.load_flash_only=false +support-files = + subtst_contextmenu_webext.html + test_contextmenu_links.html + subtst_contextmenu.html + subtst_contextmenu_input.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 + +[browser_contextmenu_loadblobinnewtab.js] +support-files = browser_contextmenu_loadblobinnewtab.html +[browser_contextmenu_save_blocked.js] +[browser_contextmenu_spellcheck.js] +skip-if = toolkit == "gtk" || (os == "win" && processor == "aarch64") # disabled on Linux due to bug 513558, aarch64 due to 1533161 +[browser_view_image.js] +support-files = + test_view_image_revoked_cached_blob.html +[browser_contextmenu_touch.js] +skip-if = true # Bug 1424433, disable due to very high frequency failure rate also on Windows 10 +[browser_contextmenu_linkopen.js] +[browser_contextmenu_iframe.js] +support-files = + test_contextmenu_iframe.html +[browser_utilityOverlay.js] +[browser_utilityOverlayPrincipal.js] +[browser_contextmenu_childprocess.js] +[browser_contextmenu.js] +tags = fullscreen +skip-if = toolkit == "gtk" || verify || (os == "win" && processor == "aarch64") # disabled on Linux due to bug 513558, aarch64 due to 1531590 +[browser_contextmenu_input.js] +skip-if = toolkit == "gtk" || (os == "win" && processor == "aarch64") # disabled on Linux due to bug 513558, aarch64 due to 1533161 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..9ff50c091c --- /dev/null +++ b/browser/base/content/test/contextMenu/browser_contextmenu.js @@ -0,0 +1,2058 @@ +"use strict"; + +let contextMenu; +let LOGIN_FILL_ITEMS = [ + "---", + null, + "fill-login", + null, + [ + "fill-login-no-logins", + false, + "---", + null, + "fill-login-saved-passwords", + true, + ], + null, +]; +let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled"); +let hasContainers = + Services.prefs.getBoolPref("privacy.userContext.enabled") && + ContextualIdentityService.getPublicIdentities().length; + +const example_base = + "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 +); + +/* import-globals-from ../general/head.js */ +Services.scriptloader.loadSubScript(head_base + "head.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_task(async function init() { + // Ensure screenshots is really disabled (bug 1498738). + const addon = await AddonManager.getAddonByID("screenshots@mozilla.org"); + await addon.disable({ allowSystemAddons: true }); + await SpecialPowers.pushPrefEnv({ + set: [["browser.search.separatePrivateDefault.ui.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, + "context-searchselect", + true, + "context-searchselect-private", + true, + "---", + null, + "context-sendlinktodevice", + true, + [], + null, + ]); + + // 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 SpecialPowers.pushPrefEnv({ + set: [["dom.menuitem.enabled", true]], + }); + + 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() { + plainTextItems = [ + "context-navigation", + null, + [ + "context-back", + false, + "context-forward", + false, + "context-reload", + true, + "context-bookmarkpage", + true, + ], + null, + "---", + null, + "context-savepage", + true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", + null, + "context-sendpagetodevice", + true, + [], + null, + "---", + null, + "context-viewbgimage", + false, + "context-selectall", + true, + "---", + null, + "context-viewsource", + true, + "context-viewinfo", + true, + ]; + await test_contextmenu("#test-text", plainTextItems, { + maybeScreenshotsPresent: true, + }); +}); + +add_task(async function test_link() { + await test_contextmenu("#test-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, + "context-searchselect", + true, + "context-searchselect-private", + true, + "---", + null, + "context-sendlinktodevice", + true, + [], + null, + ]); +}); + +add_task(async function test_link_in_shadow_dom() { + await test_contextmenu( + "#shadow-host", + [ + "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, + "context-searchselect", + true, + "context-searchselect-private", + true, + "---", + null, + "context-sendlinktodevice", + true, + [], + null, + ], + { + offsetX: 6, + offsetY: 6, + } + ); +}); + +add_task(async function test_mailto() { + await test_contextmenu("#test-mailto", [ + "context-copyemail", + true, + "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-copyimage-contents", + true, + "context-copyimage", + true, + "---", + null, + "context-saveimage", + true, + "context-sendimage", + true, + "context-setDesktopBackground", + true, + "context-viewimageinfo", + 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, + "context-selectall", + true, + ], + { + maybeScreenshotsPresent: 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-media-hidecontrols", + true, + "context-video-fullscreen", + true, + "context-video-pictureinpicture", + true, + "---", + null, + "context-viewvideo", + true, + "context-copyvideourl", + true, + "---", + null, + "context-savevideo", + true, + "context-video-saveimage", + 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-media-hidecontrols", + true, + "context-video-fullscreen", + true, + "---", + null, + "context-viewvideo", + true, + "context-copyvideourl", + true, + "---", + null, + "context-savevideo", + true, + "context-video-saveimage", + 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-copyaudiourl", + true, + "---", + null, + "context-saveaudio", + 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-media-hidecontrols", + false, + "context-video-fullscreen", + false, + "---", + null, + "context-viewvideo", + true, + "context-copyvideourl", + true, + "---", + null, + "context-savevideo", + true, + "context-video-saveimage", + false, + "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-media-hidecontrols", + false, + "context-video-fullscreen", + false, + "---", + null, + "context-viewvideo", + true, + "context-copyvideourl", + true, + "---", + null, + "context-savevideo", + true, + "context-video-saveimage", + false, + "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-media-hidecontrols", + false, + "context-video-fullscreen", + false, + "---", + null, + "context-viewvideo", + false, + "context-copyvideourl", + false, + "---", + null, + "context-savevideo", + false, + "context-video-saveimage", + 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-media-hidecontrols", + false, + "context-video-fullscreen", + false, + "---", + null, + "context-viewvideo", + false, + "context-copyvideourl", + false, + "---", + null, + "context-savevideo", + false, + "context-video-saveimage", + false, + "context-sendvideo", + false, + ]); + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function test_iframe() { + await test_contextmenu("#test-iframe", [ + "context-navigation", + null, + [ + "context-back", + false, + "context-forward", + false, + "context-reload", + true, + "context-bookmarkpage", + true, + ], + null, + "---", + null, + "context-savepage", + true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", + null, + "context-sendpagetodevice", + true, + [], + null, + "---", + null, + "context-viewbgimage", + false, + "context-selectall", + true, + "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-viewframesource", + true, + "context-viewframeinfo", + true, + ]), + null, + "---", + null, + "context-viewsource", + true, + "context-viewinfo", + 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-media-hidecontrols", + true, + "context-video-fullscreen", + true, + "context-video-pictureinpicture", + true, + "---", + null, + "context-viewvideo", + true, + "context-copyvideourl", + true, + "---", + null, + "context-savevideo", + true, + "context-video-saveimage", + true, + "context-sendvideo", + true, + "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-media-hidecontrols", + true, + "context-video-fullscreen", + true, + "---", + null, + "context-viewvideo", + true, + "context-copyvideourl", + true, + "---", + null, + "context-savevideo", + true, + "context-video-saveimage", + true, + "context-sendvideo", + true, + "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-copyaudiourl", + true, + "---", + null, + "context-saveaudio", + true, + "context-sendaudio", + true, + "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-copyimage-contents", + true, + "context-copyimage", + true, + "---", + null, + "context-saveimage", + true, + "context-sendimage", + true, + "context-setDesktopBackground", + true, + "context-viewimageinfo", + true, + "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", + [ + "context-navigation", + null, + [ + "context-back", + false, + "context-forward", + false, + "context-reload", + true, + "context-bookmarkpage", + true, + ], + null, + "---", + null, + "context-savepage", + true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", + null, + "context-sendpagetodevice", + true, + [], + null, + "context-selectall", + true, + "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, + "---", + null, + "context-viewsource", + true, + "context-viewinfo", + true, + ], + { maybeScreenshotsPresent: 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, + "---", 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, + "---", 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, { + maybeScreenshotsPresent: true, + }); +}); + +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, + "---", 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, + 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, + "---", 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} + ); + */ +}); + +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, + "http://mozilla.com/", + "paste for command cmd_paste" + ); + }); + }, + }); +}); + +add_task(async function test_pagemenu() { + await test_contextmenu( + "#test-pagemenu", + [ + "context-navigation", + null, + [ + "context-back", + false, + "context-forward", + false, + "context-reload", + true, + "context-bookmarkpage", + true, + ], + null, + "---", + null, + "+Plain item", + { type: "", icon: "", checked: false, disabled: false }, + "+Disabled item", + { type: "", icon: "", checked: false, disabled: true }, + "+Item w/ textContent", + { type: "", icon: "", checked: false, disabled: false }, + "---", + null, + "+Checkbox", + { type: "checkbox", icon: "", checked: true, disabled: false }, + "---", + null, + "+Radio1", + { type: "checkbox", icon: "", checked: true, disabled: false }, + "+Radio2", + { type: "checkbox", icon: "", checked: false, disabled: false }, + "+Radio3", + { type: "checkbox", icon: "", checked: false, disabled: false }, + "---", + null, + "+Item w/ icon", + { type: "", icon: "favicon.ico", checked: false, disabled: false }, + "+Item w/ bad icon", + { type: "", icon: "", checked: false, disabled: false }, + "---", + null, + "generated-submenu-1", + true, + [ + "+Radio1", + { type: "checkbox", icon: "", checked: false, disabled: false }, + "+Radio2", + { type: "checkbox", icon: "", checked: true, disabled: false }, + "+Radio3", + { type: "checkbox", icon: "", checked: false, disabled: false }, + "---", + null, + "+Checkbox", + { type: "checkbox", icon: "", checked: false, disabled: false }, + ], + null, + "---", + null, + "context-savepage", + true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", + null, + "context-sendpagetodevice", + true, + [], + null, + "---", + null, + "context-viewbgimage", + false, + "context-selectall", + true, + "---", + null, + "context-viewsource", + true, + "context-viewinfo", + true, + ], + { + async postCheckContextMenuFn() { + let item = contextMenu.getElementsByAttribute( + "generateditemid", + "1" + )[0]; + ok(item, "Got generated XUL menu item"); + item.doCommand(); + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + async function() { + let pagemenu = content.document.getElementById("test-pagemenu"); + Assert.ok( + !pagemenu.hasAttribute("hopeless"), + "attribute got removed" + ); + } + ); + }, + maybeScreenshotsPresent: true, + } + ); +}); + +add_task(async function test_dom_full_screen() { + await test_contextmenu( + "#test-dom-full-screen", + [ + "context-navigation", + null, + [ + "context-back", + false, + "context-forward", + false, + "context-reload", + true, + "context-bookmarkpage", + true, + ], + null, + "---", + null, + "context-leave-dom-fullscreen", + true, + "---", + null, + "context-savepage", + true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", + null, + "context-sendpagetodevice", + true, + [], + null, + "---", + null, + "context-viewbgimage", + false, + "context-selectall", + true, + "---", + null, + "context-viewsource", + true, + "context-viewinfo", + true, + ], + { + maybeScreenshotsPresent: true, + shiftkey: true, + async preCheckContextMenuFn() { + await pushPrefs( + ["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", + [ + "context-navigation", + null, + [ + "context-back", + false, + "context-forward", + false, + "context-reload", + true, + "context-bookmarkpage", + true, + ], + null, + "---", + null, + "context-savepage", + true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", + null, + "context-sendpagetodevice", + true, + [], + null, + "---", + null, + "context-viewbgimage", + false, + "context-selectall", + true, + "---", + null, + "context-viewsource", + true, + "context-viewinfo", + true, + ], + { maybeScreenshotsPresent: true, shiftkey: true } + ); +}); + +add_task(async function test_select_text() { + await test_contextmenu( + "#test-select-text", + [ + "context-copy", + true, + "context-selectall", + true, + "---", + null, + "context-searchselect", + true, + "context-searchselect-private", + true, + "context-print-selection", + true, + "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._initialized = false; + await test_contextmenu( + "#test-select-text", + [ + "context-copy", + true, + "context-selectall", + true, + "---", + null, + "context-print-selection", + true, + "context-viewpartialsource-selection", + true, + ], + { + offsetX: 6, + offsetY: 6, + async preCheckContextMenuFn() { + await selectText("#test-select-text"); + }, + } + ); + // Pretend the search service is not initialised. + Services.search.wrappedJSObject._initialized = true; +}); + +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, + "context-copy", + true, + "context-selectall", + true, + "---", + null, + "context-searchselect", + true, + "context-searchselect-private", + true, + "---", + null, + "context-sendlinktodevice", + true, + [], + null, + "context-print-selection", + true, + "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-copyimage-contents", + true, + "context-copyimage", + true, + "---", + null, + "context-saveimage", + true, + "context-sendimage", + true, + "context-setDesktopBackground", + true, + "context-viewimageinfo", + true, + "---", + null, + "context-sendlinktodevice", + true, + [], + null, + ]); +}); + +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, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", true, + "---", null, + "context-selectall", true, + "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, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", true, + "---", null, + "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-copyimage-contents", + true, + "context-copyimage", + true, + "---", + null, + "context-saveimage", + true, + "context-sendimage", + true, + "context-setDesktopBackground", + true, + "context-viewimageinfo", + true, + "context-viewimagedesc", + true, + ]); +}); + +add_task(async function test_srcdoc() { + await test_contextmenu("#test-srcdoc", [ + "context-navigation", + null, + [ + "context-back", + false, + "context-forward", + false, + "context-reload", + true, + "context-bookmarkpage", + true, + ], + null, + "---", + null, + "context-savepage", + true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", + null, + "context-sendpagetodevice", + true, + [], + null, + "---", + null, + "context-viewbgimage", + false, + "context-selectall", + true, + "frame", + null, + getThisFrameSubMenu([ + "context-reloadframe", + true, + "---", + null, + "context-saveframe", + true, + "---", + null, + "context-printframe", + true, + "---", + null, + "context-viewframesource", + true, + "context-viewframeinfo", + true, + ]), + null, + "---", + null, + "context-viewsource", + true, + "context-viewinfo", + 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, + "---", null, + "context-cut", true, + "context-copy", true, + "context-paste", null, // ignore clipboard state + "context-delete", false, + "---", null, + "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, + "context-searchselect", + true, + "context-searchselect-private", + true, + "---", + null, + "context-sendlinktodevice", + true, + [], + null, + ]); + + 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, + "context-searchselect", + true, + "context-searchselect-private", + true, + "---", + null, + "context-sendlinktodevice", + true, + [], + null, + ]); + + 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, + "context-searchselect", + true, + "context-searchselect-private", + true, + "---", + null, + "context-sendlinktodevice", + true, + [], + null, + ]); +}); + +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, + "context-searchselect", + true, + "context-searchselect-private", + true, + "---", + null, + "context-sendlinktodevice", + true, + [], + null, + ]); + + 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, + "context-searchselect", + true, + "context-searchselect-private", + true, + "---", + null, + "context-sendlinktodevice", + true, + [], + null, + ]); + + 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, + "context-searchselect", + true, + "context-searchselect-private", + true, + "---", + null, + "context-sendlinktodevice", + true, + [], + null, + ]); +}); + +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); + } + ); +} diff --git a/browser/base/content/test/contextMenu/browser_contextmenu_childprocess.js b/browser/base/content/test/contextMenu/browser_contextmenu_childprocess.js new file mode 100644 index 0000000000..af85d0a62c --- /dev/null +++ b/browser/base/content/test/contextMenu/browser_contextmenu_childprocess.js @@ -0,0 +1,129 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const gBaseURL = + "https://example.com/browser/browser/base/content/test/contextMenu/"; + +add_task(async function() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.menuitem.enabled", true]], + }); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + gBaseURL + "subtst_contextmenu.html" + ); + + let contextMenu = document.getElementById("contentAreaContextMenu"); + + // Get the point of the element with the page menu (test-pagemenu) and + // synthesize a right mouse click there. + let popupShownPromise = BrowserTestUtils.waitForEvent( + contextMenu, + "popupshown" + ); + await BrowserTestUtils.synthesizeMouse( + "#test-pagemenu", + 5, + 5, + { type: "contextmenu", button: 2 }, + tab.linkedBrowser + ); + await popupShownPromise; + + checkMenu(contextMenu); + + let popupHiddenPromise = BrowserTestUtils.waitForEvent( + contextMenu, + "popuphidden" + ); + contextMenu.hidePopup(); + await popupHiddenPromise; + + BrowserTestUtils.removeTab(tab); +}); + +function checkItems(menuitem, arr) { + for (let i = 0; i < arr.length; i += 2) { + let str = arr[i]; + let details = arr[i + 1]; + if (str == "---") { + is(menuitem.localName, "menuseparator", "menuseparator"); + } else if ("children" in details) { + is(menuitem.localName, "menu", "submenu"); + is(menuitem.getAttribute("label"), str, str + " label"); + checkItems(menuitem.menupopup.firstElementChild, details.children); + } else { + is(menuitem.localName, "menuitem", str + " menuitem"); + + is(menuitem.getAttribute("label"), str, str + " label"); + is(menuitem.getAttribute("type"), details.type, str + " type"); + is( + menuitem.getAttribute("image"), + details.icon ? gBaseURL + details.icon : "", + str + " icon" + ); + + if (details.checked) { + is(menuitem.getAttribute("checked"), "true", str + " checked"); + } else { + ok(!menuitem.hasAttribute("checked"), str + " checked"); + } + + if (details.disabled) { + is(menuitem.getAttribute("disabled"), "true", str + " disabled"); + } else { + ok(!menuitem.hasAttribute("disabled"), str + " disabled"); + } + } + + menuitem = menuitem.nextElementSibling; + } +} + +function checkMenu(contextMenu) { + let items = [ + "Plain item", + { type: "", icon: "", checked: false, disabled: false }, + "Disabled item", + { type: "", icon: "", checked: false, disabled: true }, + "Item w/ textContent", + { type: "", icon: "", checked: false, disabled: false }, + "---", + null, + "Checkbox", + { type: "checkbox", icon: "", checked: true, disabled: false }, + "---", + null, + "Radio1", + { type: "checkbox", icon: "", checked: true, disabled: false }, + "Radio2", + { type: "checkbox", icon: "", checked: false, disabled: false }, + "Radio3", + { type: "checkbox", icon: "", checked: false, disabled: false }, + "---", + null, + "Item w/ icon", + { type: "", icon: "favicon.ico", checked: false, disabled: false }, + "Item w/ bad icon", + { type: "", icon: "", checked: false, disabled: false }, + "---", + null, + "Submenu", + { + children: [ + "Radio1", + { type: "checkbox", icon: "", checked: false, disabled: false }, + "Radio2", + { type: "checkbox", icon: "", checked: true, disabled: false }, + "Radio3", + { type: "checkbox", icon: "", checked: false, disabled: false }, + "---", + null, + "Checkbox", + { type: "checkbox", icon: "", checked: false, disabled: false }, + ], + }, + ]; + checkItems(contextMenu.children[2], items); +} 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..0663f0d713 --- /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 menuPopup = contextMenu.querySelector("#frame").menupopup; + const menuPopupPromise = BrowserTestUtils.waitForEvent( + menuPopup, + "popupshown" + ); + menuPopup.openPopup(); + 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"); + domItem.click(); + + let openedTab = await openPromise; + contextMenu.hidePopup(); + 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..94e9465fd5 --- /dev/null +++ b/browser/base/content/test/contextMenu/browser_contextmenu_input.js @@ -0,0 +1,332 @@ +"use strict"; + +let contextMenu; +let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled"); + +add_task(async function test_setup() { + const example_base = + "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); + + // Ensure screenshots is really disabled (bug 1498738) + const addon = await AddonManager.getAddonByID("screenshots@mozilla.org"); + await addon.disable({ allowSystemAddons: true }); +}); + +add_task(async function test_text_input() { + await test_contextmenu("#input_text", [ + "context-undo", + false, + "---", + null, + "context-cut", + true, + "context-copy", + true, + "context-paste", + null, // ignore clipboard state + "context-delete", + false, + "---", + null, + "context-selectall", + false, + "---", + null, + "spell-check-enabled", + true, + ]); +}); + +add_task(async function test_text_input_disabled() { + await test_contextmenu( + "#input_disabled", + [ + "context-undo", + false, + "---", + null, + "context-cut", + true, + "context-copy", + true, + "context-paste", + null, // ignore clipboard state + "context-delete", + false, + "---", + null, + "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]], + }); + todo( + false, + "context-selectall is enabled on osx-e10s, and windows when" + + " it should be disabled" + ); + await test_contextmenu( + "#input_password", + [ + "fill-login", + null, + [ + "fill-login-no-logins", + false, + "---", + null, + "fill-login-saved-passwords", + true, + ], + null, + "---", + null, + "context-undo", + false, + "---", + null, + "context-cut", + true, + "context-copy", + true, + "context-paste", + null, // ignore clipboard state + "context-delete", + false, + "---", + null, + "context-selectall", + 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 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, + "---", + null, + "context-cut", + true, + "context-copy", + true, + "context-paste", + null, // ignore clipboard state + "context-delete", + false, + "---", + null, + "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, + [ + "context-navigation", + null, + [ + "context-back", + false, + "context-forward", + false, + "context-reload", + true, + "context-bookmarkpage", + true, + ], + null, + "---", + null, + "context-savepage", + true, + ...(hasPocket ? ["context-pocket", true] : []), + "---", + null, + "context-sendpagetodevice", + null, + [], + null, + "---", + null, + "context-viewbgimage", + false, + "context-selectall", + null, + "---", + null, + "context-viewsource", + true, + "context-viewinfo", + true, + ], + { + // XXX Bug 1345081. Currently the Screenshots menu option is shown for + // various text elements even though it is set to type "page". That bug + // should remove the need for next line. + maybeScreenshotsPresent: 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, + "---", + null, + "context-cut", + true, + "context-copy", + true, + "context-paste", + null, // ignore clipboard state + "context-delete", + false, + "---", + null, + "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, + "---", + null, + "context-cut", + true, + "context-copy", + true, + "context-paste", + null, // ignore clipboard state + "context-delete", + false, + "---", + null, + "context-selectall", + null, + ], + { + // XXX Bug 1345081. Currently the Screenshots menu option is shown for + // various text elements even though it is set to type "page". That bug + // should remove the need for next line. + maybeScreenshotsPresent: true, + skipFocusChange: true, + } + ); +}); + +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..1b9c6e2809 --- /dev/null +++ b/browser/base/content/test/contextMenu/browser_contextmenu_linkopen.js @@ -0,0 +1,111 @@ +/* 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); + let bounds = domItem.getBoundingClientRect(); + ok( + bounds.height && bounds.width, + "DOM context menu item " + where + " should be visible" + ); + ok( + !domItem.disabled, + "DOM context menu item " + where + " shouldn't be disabled" + ); + domItem.click(); + contextMenu.hidePopup(); + 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..d8e7be99bf --- /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.loadURI(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.loadURI(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_task(async function setup() { + 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..e3c8ac6145 --- /dev/null +++ b/browser/base/content/test/contextMenu/browser_contextmenu_save_blocked.js @@ -0,0 +1,80 @@ +/* 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: { + applications: { gecko: { id: "cancel@test" } }, + name: "Cancel Test", + permissions: ["webRequest", "webRequestBlocking", "<all_urls>"], + }, + + background() { + // eslint-disable-next-line no-undef + browser.webRequest.onBeforeRequest.addListener( + details => { + 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; + }; + EventUtils.synthesizeMouseAtCenter( + menu.querySelector("#context-savelink"), + {} + ); + }); + } + ); + + await ext.unload(); +}); 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..3a150fecff --- /dev/null +++ b/browser/base/content/test/contextMenu/browser_contextmenu_spellcheck.js @@ -0,0 +1,251 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +let contextMenu; + +const example_base = + "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); + + // Ensure screenshots is really disabled (bug 1498738) + const addon = await AddonManager.getAddonByID("screenshots@mozilla.org"); + await addon.disable({ allowSystemAddons: true }); +}); + +add_task(async function test_text_input_spellcheck() { + await test_contextmenu( + "#input_spellcheck_no_value", + [ + "context-undo", + 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, + "---", + 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, + "---", + null, + "context-cut", + null, + "context-copy", + null, + "context-paste", + null, // ignore clipboard state + "context-delete", + null, + "---", + 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, + "---", + null, + "context-cut", + null, + "context-copy", + null, + "context-paste", + null, // ignore clipboard state + "context-delete", + null, + "---", + 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.loadURI(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.loadURI(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.loadURI(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_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..81e6462a38 --- /dev/null +++ b/browser/base/content/test/contextMenu/browser_contextmenu_touch.js @@ -0,0 +1,91 @@ +/* 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_task(async function setup() { + 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() { + 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") + ); + BrowserTestUtils.loadURI(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_utilityOverlay.js b/browser/base/content/test/contextMenu/browser_utilityOverlay.js new file mode 100644 index 0000000000..ff470b2a15 --- /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_getTopWin() { + is(getTopWin(), window, "got top window"); +}); + +add_task(async function test_openUILink() { + const kURL = "http://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..2fe9b2c9a0 --- /dev/null +++ b/browser/base/content/test/contextMenu/browser_utilityOverlayPrincipal.js @@ -0,0 +1,68 @@ +/* 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, + "http://example.com/" + )); // remote tab + BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(async function() { + is( + tab.linkedBrowser.currentURI.spec, + "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, + "http://example.com/", + "sanity: correct doc.nodePrincipal URL" + ); + }); + + gBrowser.removeCurrentTab(); + runNextTest(); + }); + + // Ensure we get the correct default of "allowInheritPrincipal: false" from openUILink + 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..36496a2327 --- /dev/null +++ b/browser/base/content/test/contextMenu/browser_view_image.js @@ -0,0 +1,148 @@ +/* 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 }) { + let mainURL = http_base + page; + let accel = AppConstants.platform == "macosx" ? "metaKey" : "ctrlKey"; + let tests = { + tab: { + event: { [accel]: true }, + async loadedPromise() { + return BrowserTestUtils.waitForNewTab( + gBrowser, + url => url.startsWith("blob"), + true + ).then(t => t.linkedBrowser); + }, + cleanup(browser) { + BrowserTestUtils.removeTab(gBrowser.getTabForBrowser(browser)); + }, + }, + window: { + event: { 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() && getCx().currentWindowGlobal.documentURI.schemeIs("blob") + ); + await SpecialPowers.spawn(browser, [], () => { + return ContentTaskUtils.waitForCondition( + () => content.document.readyState == "complete" + ); + }); + return browser; + }, + async cleanup(browser) { + return BrowserTestUtils.closeWindow(browser.ownerGlobal); + }, + }, + self: { + event: {}, + async loadedPromise() { + await BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + url => url.startsWith("blob:") + ); + return gBrowser.selectedBrowser; + }, + async cleanup() {}, + }, + // NOTE: If we ever add more tests here, add them above and not below `self`, as it replaces + // the test document. + }; + 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)) { + 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(); + EventUtils.synthesizeMouseAtCenter( + document.getElementById("context-viewimage"), + test.event + ); + await promisePopupHidden; + + let newBrowser = await browserPromise; + 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); + } + }); +} + +/** + * 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", + }); +}); + +/** + * 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", + }); +}); 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..6a340367e6 --- /dev/null +++ b/browser/base/content/test/contextMenu/contextmenu_common.js @@ -0,0 +1,474 @@ +// 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.Cu.import( + "resource://testing-common/AsyncSpellCheckTestHelper.jsm", + {} + ); + 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(); + } + + var isPageMenuItem = item.hasAttribute("generateditemid"); + + 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 if (isPageMenuItem) { + is( + item.id, + "", + "child menuitem #" + i + " is a generated page menu item" + ); + } 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 (isPageMenuItem) { + 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-saved-passwords" && + item.id != "fill-login-no-logins" && + // XXX Screenshots doesn't have an access key. This needs + // at least bug 1320462 fixing first. + item.id != "screenshots_mozilla_org-menuitem-_create-screenshot" && + // Inspect accessibility properties does not have an access key. See + // bug 1630717 for more details. + item.id != "context-inspect-a11y" + ) { + 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 && !isPageMenuItem) { + items.push(item.id); + } + if (isPageMenuItem) { + var p = {}; + p.type = item.getAttribute("type"); + p.icon = item.getAttribute("image"); + p.checked = item.hasAttribute("checked"); + p.disabled = item.hasAttribute("disabled"); + items.push(p); + } else { + 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") { + if (isPageMenuItem) { + item.id = "generated-submenu-" + aData.generatedSubmenuId++; + } + ok(item.id, "child menu #" + i + " has an ID"); + if (!isPageMenuItem) { + 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 + * maybeScreenshotsPresent: if true, the screenshots menu entry is + * expected to be present in the menu if + * screenshots is enabled, optional + * 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.import( + "resource://testing-common/AsyncSpellCheckTestHelper.jsm" + ); + 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 = ["---", null]; + if (Services.prefs.getBoolPref("devtools.accessibility.enabled", true)) { + inspectItems.push("context-inspect-a11y", true); + } + + inspectItems.push("context-inspect", true); + menuItems = menuItems.concat(inspectItems); + } + + if ( + options.maybeScreenshotsPresent && + !Services.prefs.getBoolPref("extensions.screenshots.disabled", false) + ) { + let screenshotItems = [ + "---", + null, + "screenshots_mozilla_org-menuitem-_create-screenshot", + true, + ]; + + menuItems = menuItems.concat(screenshotItems); + } + + 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/subtst_contextmenu.html b/browser/base/content/test/contextMenu/subtst_contextmenu.html new file mode 100644 index 0000000000..68f2bf4fae --- /dev/null +++ b/browser/base/content/test/contextMenu/subtst_contextmenu.html @@ -0,0 +1,90 @@ +<!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> +<script> +// Create the shadow DOM in case shadow DOM is enabled. +if ("ShadowRoot" in this) { + var sr = document.getElementById("shadow-host").attachShadow({mode: "closed"}); + sr.innerHTML = "<a href='http://mozilla.com'>Click the monkey!</a>"; +} +</script> +<a id="test-mailto" href="mailto:codemonkey@mozilla.com">Mail the monkey!</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 contextmenu="myMenu"> + <p id="test-pagemenu" hopeless="true">I've got a context menu!</p> + <menu id="myMenu" type="context"> + <menuitem label="Plain item" onclick="document.getElementById('test-pagemenu').removeAttribute('hopeless');"></menuitem> + <menuitem label="Disabled item" disabled></menuitem> + <menuitem> Item w/ textContent</menuitem> + <menu> + <menuitem type="checkbox" label="Checkbox" checked></menuitem> + </menu> + <menu> + <menuitem type="radio" label="Radio1" checked></menuitem> + <menuitem type="radio" label="Radio2"></menuitem> + <menuitem type="radio" label="Radio3"></menuitem> + </menu> + <menu> + <menuitem label="Item w/ icon" icon="favicon.ico"></menuitem> + <menuitem label="Item w/ bad icon" icon="http://example.com%0a%23.google.com/"></menuitem> + </menu> + <menu label="Submenu"> + <menuitem type="radio" label="Radio1" radiogroup="rg"></menuitem> + <menuitem type="radio" label="Radio2" checked radiogroup="rg"></menuitem> + <menuitem type="radio" label="Radio3" radiogroup="rg"></menuitem> + <menu> + <menuitem type="checkbox" label="Checkbox"></menuitem> + </menu> + </menu> + <menu hidden> + <menuitem label="Bogus item"></menuitem> + </menu> + <menu> + </menu> + <menuitem label="Hidden item" hidden></menuitem> + <menuitem></menuitem> + </menu> +</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> +</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..f7921f6833 --- /dev/null +++ b/browser/base/content/test/contextMenu/subtst_contextmenu_input.html @@ -0,0 +1,29 @@ +<!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"> +</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_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> |